Initial fork from spacecraft-tactics
This commit is contained in:
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
/target/
|
||||||
|
/.classpath
|
||||||
|
/.project
|
||||||
|
/.settings/
|
||||||
36
pom.xml
Normal file
36
pom.xml
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>de.extio</groupId>
|
||||||
|
<artifactId>game-engine</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<java.version>25</java.version>
|
||||||
|
<maven.compiler.source>25</maven.compiler.source>
|
||||||
|
<maven.compiler.target>25</maven.compiler.target>
|
||||||
|
<slf4j.version>2.0.17</slf4j.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-api</artifactId>
|
||||||
|
<version>${slf4j.version}</version>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-simple</artifactId>
|
||||||
|
<version>${slf4j.version}</version>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||||
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||||
|
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package de.extio.game_engine.keyboard;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
|
||||||
|
public final class KeyModifiers {
|
||||||
|
|
||||||
|
public final static int MODIFIER_NONE = 0;
|
||||||
|
|
||||||
|
public final static int MODIFIER_SHIFT = 1;
|
||||||
|
|
||||||
|
public final static int MODIFIER_CTRL = 2;
|
||||||
|
|
||||||
|
public final static int MODIFIER_ALT = 4;
|
||||||
|
|
||||||
|
public final static int MODIFIER_ALTGR = 8;
|
||||||
|
|
||||||
|
private final static Map<Integer, String> MODIFIER_LABELS = Map.of(
|
||||||
|
MODIFIER_SHIFT, "SHIFT ",
|
||||||
|
MODIFIER_CTRL, "CTRL ",
|
||||||
|
MODIFIER_ALT, "ALT ",
|
||||||
|
MODIFIER_ALTGR, "ALTGR ");
|
||||||
|
|
||||||
|
public static String getModifierLabel(final int modifier) {
|
||||||
|
final StringBuilder label = new StringBuilder(12);
|
||||||
|
for (final Entry<Integer, String> entry : MODIFIER_LABELS.entrySet()) {
|
||||||
|
if ((modifier & entry.getKey().intValue()) != 0) {
|
||||||
|
label.append(entry.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return label.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||||
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||||
|
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package de.extio.game_engine.keyboard;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public final class KeycodeRegistration {
|
||||||
|
|
||||||
|
private String qualifier;
|
||||||
|
|
||||||
|
private String displayText;
|
||||||
|
|
||||||
|
private int code;
|
||||||
|
|
||||||
|
private int modifier;
|
||||||
|
|
||||||
|
private String keyCodeDisplay;
|
||||||
|
|
||||||
|
public KeycodeRegistration() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeycodeRegistration(final String qualifier, final String displayText, final int code, final int modifier, final String keyCodeDisplay) {
|
||||||
|
this.qualifier = qualifier;
|
||||||
|
this.displayText = displayText;
|
||||||
|
this.code = code;
|
||||||
|
this.modifier = modifier;
|
||||||
|
this.keyCodeDisplay = keyCodeDisplay;
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeycodeRegistration(final KeycodeRegistration other) {
|
||||||
|
this.qualifier = other.qualifier;
|
||||||
|
this.displayText = other.displayText;
|
||||||
|
this.code = other.code;
|
||||||
|
this.modifier = other.modifier;
|
||||||
|
this.keyCodeDisplay = other.keyCodeDisplay;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getQualifier() {
|
||||||
|
return this.qualifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setQualifier(final String qualifier) {
|
||||||
|
this.qualifier = qualifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCode() {
|
||||||
|
return this.code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCode(final int code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getModifier() {
|
||||||
|
return this.modifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setModifier(final int modifier) {
|
||||||
|
this.modifier = modifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDisplayText() {
|
||||||
|
return this.displayText;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDisplayText(final String displayText) {
|
||||||
|
this.displayText = displayText;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getKeyCodeDisplay() {
|
||||||
|
return this.keyCodeDisplay;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setKeyCodeDisplay(final String keyCodeDisplay) {
|
||||||
|
this.keyCodeDisplay = keyCodeDisplay;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(this.code, this.displayText, this.keyCodeDisplay, this.modifier, this.qualifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(obj instanceof KeycodeRegistration)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final KeycodeRegistration other = (KeycodeRegistration) obj;
|
||||||
|
return this.code == other.code && Objects.equals(this.displayText, other.displayText) && Objects.equals(this.keyCodeDisplay, other.keyCodeDisplay) && this.modifier == other.modifier && Objects.equals(this.qualifier, other.qualifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||||
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||||
|
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package de.extio.game_engine.keyboard;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key code registry let's modules register key codes and returns the actual keys configured by the user
|
||||||
|
*/
|
||||||
|
public interface KeycodeRegistry {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the key configured for key registrations - usually used by options menu for the user to change keys
|
||||||
|
*/
|
||||||
|
void register(KeycodeRegistration... keycodeRegistrations);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers key code registrations also containing the default key - usually used by modules making use of these key presses
|
||||||
|
*/
|
||||||
|
void registerDefault(KeycodeRegistration... keycodeRegistrations);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregisters key code registrations
|
||||||
|
*/
|
||||||
|
void unregister(String... qualifiers);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current registered key code registration by qualifier
|
||||||
|
*/
|
||||||
|
KeycodeRegistration get(String qualifier);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current registered key code registration by code
|
||||||
|
*/
|
||||||
|
List<KeycodeRegistration> get(int code);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all key code registrations
|
||||||
|
*/
|
||||||
|
Collection<KeycodeRegistration> getAll();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the key code and modifiers (ctrl, alt, ...) match to a certain registration. This method is used by modules in key event handlers to check whether their key was pressed
|
||||||
|
*/
|
||||||
|
boolean check(String qualifier, int code, int modifiers);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
package de.extio.game_engine.keyboard;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class KeycodeRegistryImpl implements KeycodeRegistry {
|
||||||
|
|
||||||
|
private volatile Map<String, KeycodeRegistration> registry;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void register(final KeycodeRegistration... keycodeRegistrations) {
|
||||||
|
this.lazyLoadRegistry();
|
||||||
|
|
||||||
|
for (final KeycodeRegistration keycodeRegistration : keycodeRegistrations) {
|
||||||
|
this.registry.put(keycodeRegistration.getQualifier(), new KeycodeRegistration(keycodeRegistration));
|
||||||
|
}
|
||||||
|
|
||||||
|
// EngineFacade.instance().storePersistentClientState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void registerDefault(final KeycodeRegistration... keycodeRegistrations) {
|
||||||
|
this.lazyLoadRegistry();
|
||||||
|
|
||||||
|
boolean added = false;
|
||||||
|
for (final KeycodeRegistration keycodeRegistration : keycodeRegistrations) {
|
||||||
|
added |= this.registry.putIfAbsent(keycodeRegistration.getQualifier(), new KeycodeRegistration(keycodeRegistration)) == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (added) {
|
||||||
|
// EngineFacade.instance().storePersistentClientState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unregister(final String... qualifiers) {
|
||||||
|
this.lazyLoadRegistry();
|
||||||
|
|
||||||
|
for (final String qualifier : qualifiers) {
|
||||||
|
this.registry.remove(qualifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
// EngineFacade.instance().storePersistentClientState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public KeycodeRegistration get(final String qualifier) {
|
||||||
|
this.lazyLoadRegistry();
|
||||||
|
|
||||||
|
return this.registry.get(qualifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<KeycodeRegistration> get(final int code) {
|
||||||
|
this.lazyLoadRegistry();
|
||||||
|
|
||||||
|
return this.registry.values()
|
||||||
|
.stream()
|
||||||
|
.filter(reg -> reg.getCode() == code)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<KeycodeRegistration> getAll() {
|
||||||
|
this.lazyLoadRegistry();
|
||||||
|
|
||||||
|
return this.registry.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean check(final String qualifier, final int code, final int modifiers) {
|
||||||
|
this.lazyLoadRegistry();
|
||||||
|
|
||||||
|
final KeycodeRegistration reg = this.registry.get(qualifier);
|
||||||
|
if (reg == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return reg.getCode() == code && reg.getModifier() == modifiers;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private void lazyLoadRegistry() {
|
||||||
|
if (this.registry == null) {
|
||||||
|
synchronized (this) {
|
||||||
|
if (this.registry == null) {
|
||||||
|
// this.registry = (Map<String, KeycodeRegistration>) this.clientEngineData.getPersistentClientState().state.get("KeycodeRegistry");
|
||||||
|
// if (this.registry == null) {
|
||||||
|
this.registry = Collections.synchronizedMap(new LinkedHashMap<>());
|
||||||
|
// this.clientEngineData.getPersistentClientState().state.put("KeycodeRegistry", this.registry);
|
||||||
|
// EngineFacade.instance().storePersistentClientState();
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
19
src/main/java/de/extio/game_engine/renderer/Renderer.java
Normal file
19
src/main/java/de/extio/game_engine/renderer/Renderer.java
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package de.extio.game_engine.renderer;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import de.extio.game_engine.renderer.model.RenderingBo;
|
||||||
|
|
||||||
|
public interface Renderer {
|
||||||
|
|
||||||
|
void show();
|
||||||
|
|
||||||
|
void run(List<RenderingBo> renderingBOs) throws InterruptedException;
|
||||||
|
|
||||||
|
void reset();
|
||||||
|
|
||||||
|
void takeScreenshot();
|
||||||
|
|
||||||
|
void setTitle(final String title);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package de.extio.game_engine.renderer;
|
||||||
|
|
||||||
|
import de.extio.game_engine.spatial2.model.CoordI2;
|
||||||
|
import de.extio.game_engine.spatial2.model.ImmutableCoordI2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RendererControl controls the renderer
|
||||||
|
*/
|
||||||
|
public interface RendererControl {
|
||||||
|
|
||||||
|
public final static CoordI2 REFERENCE_RESOLUTION = ImmutableCoordI2.create(1920, 1080);
|
||||||
|
|
||||||
|
public final static double SCALE_FACTOR_MIN = 2.0 / 3.0;
|
||||||
|
|
||||||
|
public final static double SCALE_FACTOR_MAX = 4.0;
|
||||||
|
|
||||||
|
void setTitle(String title);
|
||||||
|
|
||||||
|
void applyVideoOptions();
|
||||||
|
|
||||||
|
long getVideoOptionsAppliedAt();
|
||||||
|
|
||||||
|
CoordI2 getAbsoluteViewportDimension();
|
||||||
|
|
||||||
|
CoordI2 getEffectiveViewportDimension();
|
||||||
|
|
||||||
|
double getScaleFactor();
|
||||||
|
|
||||||
|
double getScaleFactorModifier();
|
||||||
|
|
||||||
|
void setScaleFactorModifier(double scaleFactorModifier);
|
||||||
|
|
||||||
|
boolean isForceAutoScaling();
|
||||||
|
|
||||||
|
void setForceAutoScaling(boolean force);
|
||||||
|
|
||||||
|
int getFrameRate();
|
||||||
|
|
||||||
|
void setFrameRate(int frameRate);
|
||||||
|
|
||||||
|
long getFrame();
|
||||||
|
|
||||||
|
}
|
||||||
214
src/main/java/de/extio/game_engine/renderer/RendererData.java
Normal file
214
src/main/java/de/extio/game_engine/renderer/RendererData.java
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
package de.extio.game_engine.renderer;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import de.extio.game_engine.spatial2.model.CoordI2;
|
||||||
|
import de.extio.game_engine.spatial2.model.MutableCoordI2;
|
||||||
|
|
||||||
|
public class RendererData {
|
||||||
|
|
||||||
|
private Renderer renderer;
|
||||||
|
|
||||||
|
private RendererControl rendererControl;
|
||||||
|
|
||||||
|
private RenderingBoPool renderingBoPool;
|
||||||
|
|
||||||
|
private final UiOptions uiOptions = new UiOptions();
|
||||||
|
|
||||||
|
private final VideoOptions videoOptions = new VideoOptions();
|
||||||
|
|
||||||
|
private long frame;
|
||||||
|
|
||||||
|
private Consumer<Object> eventConsumer;
|
||||||
|
|
||||||
|
public final class UiOptions {
|
||||||
|
|
||||||
|
private String backgroundResourceName0 = null;
|
||||||
|
|
||||||
|
private boolean backgroundScrolling0;
|
||||||
|
|
||||||
|
private boolean backgroundScrollingReverse0;
|
||||||
|
|
||||||
|
private boolean backgroundScrolling1;
|
||||||
|
|
||||||
|
private boolean backgroundScrollingReverse1;
|
||||||
|
|
||||||
|
private boolean drawFps;
|
||||||
|
|
||||||
|
private CoordI2 backgroundOffset0 = MutableCoordI2.create();
|
||||||
|
|
||||||
|
private CoordI2 backgroundOffset1 = MutableCoordI2.create();
|
||||||
|
|
||||||
|
public UiOptions() {
|
||||||
|
if ("true".equals(System.getProperty("debug"))) {
|
||||||
|
this.drawFps = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDrawFps() {
|
||||||
|
return this.drawFps;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDrawFps(final boolean drawFps) {
|
||||||
|
this.drawFps = drawFps;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBackgroundResourceName0() {
|
||||||
|
return this.backgroundResourceName0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBackgroundResourceName0(final String background0) {
|
||||||
|
this.backgroundResourceName0 = background0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isBackgroundScrolling0() {
|
||||||
|
return this.backgroundScrolling0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBackgroundScrolling0(final boolean backgroundScrolling0) {
|
||||||
|
this.backgroundScrolling0 = backgroundScrolling0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isBackgroundScrollingReverse0() {
|
||||||
|
return this.backgroundScrollingReverse0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBackgroundScrollingReverse0(final boolean backgroundScrollingReverse0) {
|
||||||
|
this.backgroundScrollingReverse0 = backgroundScrollingReverse0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isBackgroundScrolling1() {
|
||||||
|
return this.backgroundScrolling1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBackgroundScrolling1(final boolean backgroundScrolling1) {
|
||||||
|
this.backgroundScrolling1 = backgroundScrolling1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isBackgroundScrollingReverse1() {
|
||||||
|
return this.backgroundScrollingReverse1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBackgroundScrollingReverse1(final boolean backgroundScrollingReverse1) {
|
||||||
|
this.backgroundScrollingReverse1 = backgroundScrollingReverse1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CoordI2 getBackgroundOffset0() {
|
||||||
|
return this.backgroundOffset0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBackgroundOffset0(final CoordI2 backgroundOffset0) {
|
||||||
|
this.backgroundOffset0 = backgroundOffset0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CoordI2 getBackgroundOffset1() {
|
||||||
|
return this.backgroundOffset1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBackgroundOffset1(final CoordI2 backgroundOffset1) {
|
||||||
|
this.backgroundOffset1 = backgroundOffset1;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class VideoOptions {
|
||||||
|
|
||||||
|
private VideoOptionsVideoMode videoMode = VideoOptionsVideoMode.BORDERLESS;
|
||||||
|
|
||||||
|
private int fullScreenNumber = -1;
|
||||||
|
|
||||||
|
private double scaleFactorModifier = 1.0;
|
||||||
|
|
||||||
|
private int frameRateTarget = 60;
|
||||||
|
|
||||||
|
public double getScaleFactorModifier() {
|
||||||
|
return this.scaleFactorModifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScaleFactorModifier(final double scaleFactorModifier) {
|
||||||
|
this.scaleFactorModifier = scaleFactorModifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VideoOptionsVideoMode getVideoMode() {
|
||||||
|
return this.videoMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVideoMode(final VideoOptionsVideoMode videoMode) {
|
||||||
|
this.videoMode = videoMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getFullScreenNumber() {
|
||||||
|
return this.fullScreenNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFullScreenNumber(final int fullScreenNumber) {
|
||||||
|
this.fullScreenNumber = fullScreenNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getFrameRateTarget() {
|
||||||
|
return this.frameRateTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFrameRateTarget(final int frameRateTarget) {
|
||||||
|
this.frameRateTarget = frameRateTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static enum VideoOptionsVideoMode {
|
||||||
|
|
||||||
|
WINDOW,
|
||||||
|
BORDERLESS,
|
||||||
|
FULLSCREEN
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public RendererControl getRendererControl() {
|
||||||
|
return this.rendererControl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRendererControl(final RendererControl rendererControl) {
|
||||||
|
this.rendererControl = rendererControl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UiOptions getUiOptions() {
|
||||||
|
return this.uiOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RenderingBoPool getRenderingBoPool() {
|
||||||
|
return this.renderingBoPool;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRenderingBoPool(final RenderingBoPool renderingBoPool) {
|
||||||
|
this.renderingBoPool = renderingBoPool;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getFrame() {
|
||||||
|
return this.frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFrame(final long frame) {
|
||||||
|
this.frame = frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Renderer getRenderer() {
|
||||||
|
return this.renderer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRenderer(final Renderer renderer) {
|
||||||
|
this.renderer = renderer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VideoOptions getVideoOptions() {
|
||||||
|
return videoOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Consumer<Object> getEventConsumer() {
|
||||||
|
return eventConsumer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEventConsumer(Consumer<Object> eventConsumer) {
|
||||||
|
this.eventConsumer = eventConsumer;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package de.extio.game_engine.renderer;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import de.extio.game_engine.renderer.g2d.G2DRenderer;
|
||||||
|
import de.extio.game_engine.renderer.g2d.G2DRendererControl;
|
||||||
|
|
||||||
|
public class RendererFactory {
|
||||||
|
|
||||||
|
public static Renderer createG2DRenderer(final RenderingBoPool renderingBoPool, Consumer<Object> eventConsumer) {
|
||||||
|
final var rendererData = new RendererData();
|
||||||
|
final var renderer = new G2DRenderer(rendererData);
|
||||||
|
rendererData.setRendererControl(new G2DRendererControl(renderer, rendererData));
|
||||||
|
rendererData.setRenderingBoPool(renderingBoPool);
|
||||||
|
rendererData.setRenderer(renderer);
|
||||||
|
rendererData.setEventConsumer(eventConsumer);
|
||||||
|
|
||||||
|
return renderer;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||||
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||||
|
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package de.extio.game_engine.renderer;
|
||||||
|
|
||||||
|
import de.extio.game_engine.renderer.model.RenderingBo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pool for rendering business objects.
|
||||||
|
* To render something, acquire a rendering business object from this manager, set all properties and add it to <i>List<RenderingBo> renderingBo</i> which is passed to various callback methods that are part of the rendering pipeline.
|
||||||
|
* The renderer will then manage the bo and also takes care of releasing it back to the pool after rendering is complete.
|
||||||
|
* It is important for performance reasons to always use pooled rendering business objects instead of creating new instances with every frame - keep in mind with HFR rendering this happens 120 times per second for hundrets of bos per cycle!
|
||||||
|
* Another reason is encapsulation - your client code only operates on interfaces for rendering bos, the renderer implementation and therefore also the rendering bo implementations are interchangeable by design. This way we could switch to a different renderer without changing any other client code.
|
||||||
|
*/
|
||||||
|
public interface RenderingBoPool {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Acquires a rendering business object by type from the pool.
|
||||||
|
*/
|
||||||
|
<T extends RenderingBo> T acquire(Class<T> clazz);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Releases a rendering business object back to the pool. This is usually managed by the renderer.
|
||||||
|
*/
|
||||||
|
void release(RenderingBo obj);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
package de.extio.game_engine.renderer;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Stack;
|
||||||
|
|
||||||
|
import de.extio.game_engine.renderer.model.RenderingBo;
|
||||||
|
|
||||||
|
public class RenderingBoPoolImpl implements RenderingBoPool {
|
||||||
|
|
||||||
|
public final static Map<Class<? extends RenderingBo>, Class<? extends RenderingBo>> INTERFACE_TO_BO_G2D = new HashMap<>();
|
||||||
|
|
||||||
|
private final RendererData rendererData;
|
||||||
|
|
||||||
|
private final Map<Class<? extends RenderingBo>, Stack<RenderingBo>> pool = new HashMap<>();
|
||||||
|
|
||||||
|
private final LastMapping lastMapping = new LastMapping();
|
||||||
|
|
||||||
|
private final LastReverseMapping lastReverseMapping = new LastReverseMapping();
|
||||||
|
|
||||||
|
private static class LastMapping {
|
||||||
|
|
||||||
|
volatile Class<? extends RenderingBo> clazz;
|
||||||
|
|
||||||
|
volatile Class<? extends RenderingBo> impl;
|
||||||
|
|
||||||
|
volatile Stack<RenderingBo> pooled;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class LastReverseMapping {
|
||||||
|
|
||||||
|
volatile Class<? extends RenderingBo> clazz;
|
||||||
|
|
||||||
|
volatile Stack<RenderingBo> pooled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RenderingBoPoolImpl(final RendererData rendererData) {
|
||||||
|
this.rendererData = rendererData;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T extends RenderingBo> T acquire(final Class<T> clazz) {
|
||||||
|
Stack<RenderingBo> pooled;
|
||||||
|
Class<? extends RenderingBo> impl;
|
||||||
|
|
||||||
|
if (this.lastMapping != null && this.lastMapping.clazz == clazz) {
|
||||||
|
impl = this.lastMapping.impl;
|
||||||
|
pooled = this.lastMapping.pooled;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (!clazz.isInterface()) {
|
||||||
|
impl = clazz;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
impl = INTERFACE_TO_BO_G2D.get(clazz);
|
||||||
|
if (impl == null) {
|
||||||
|
throw new IllegalArgumentException(clazz.getSimpleName() + " not mapped");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pooled = this.pool.get(impl);
|
||||||
|
if (pooled == null) {
|
||||||
|
pooled = new Stack<>();
|
||||||
|
this.pool.put(impl, pooled);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lastMapping.clazz = clazz;
|
||||||
|
this.lastMapping.impl = impl;
|
||||||
|
this.lastMapping.pooled = pooled;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pooled.isEmpty()) {
|
||||||
|
T instance;
|
||||||
|
try {
|
||||||
|
instance = clazz.cast(impl.getDeclaredConstructor().newInstance());
|
||||||
|
}
|
||||||
|
catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
instance.setRendererData(this.rendererData);
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (T) pooled.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void release(final RenderingBo obj) {
|
||||||
|
try {
|
||||||
|
obj.close();
|
||||||
|
|
||||||
|
final Class<? extends RenderingBo> clazz = obj.getClass();
|
||||||
|
Stack<RenderingBo> pooled;
|
||||||
|
|
||||||
|
if (this.lastReverseMapping != null && this.lastReverseMapping.clazz == clazz) {
|
||||||
|
pooled = this.lastReverseMapping.pooled;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
pooled = this.pool.get(clazz);
|
||||||
|
if (pooled == null) {
|
||||||
|
pooled = new Stack<>();
|
||||||
|
this.pool.put(clazz, pooled);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lastReverseMapping.clazz = clazz;
|
||||||
|
this.lastReverseMapping.pooled = pooled;
|
||||||
|
}
|
||||||
|
|
||||||
|
pooled.push(obj);
|
||||||
|
}
|
||||||
|
catch (final Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
115
src/main/java/de/extio/game_engine/renderer/UiOptions.java
Normal file
115
src/main/java/de/extio/game_engine/renderer/UiOptions.java
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||||
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||||
|
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package de.extio.game_engine.renderer;
|
||||||
|
|
||||||
|
import de.extio.game_engine.spatial2.model.CoordI2;
|
||||||
|
import de.extio.game_engine.spatial2.model.MutableCoordI2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* General options for the client UI
|
||||||
|
*/
|
||||||
|
public final class UiOptions {
|
||||||
|
|
||||||
|
private String backgroundResourceModName0 = null;
|
||||||
|
|
||||||
|
private String backgroundResourceName0 = null;
|
||||||
|
|
||||||
|
private boolean backgroundScrolling0;
|
||||||
|
|
||||||
|
private boolean backgroundScrollingReverse0;
|
||||||
|
|
||||||
|
private boolean backgroundScrolling1;
|
||||||
|
|
||||||
|
private boolean backgroundScrollingReverse1;
|
||||||
|
|
||||||
|
private boolean drawFps;
|
||||||
|
|
||||||
|
private CoordI2 backgroundOffset0 = MutableCoordI2.create();
|
||||||
|
|
||||||
|
private CoordI2 backgroundOffset1 = MutableCoordI2.create();
|
||||||
|
|
||||||
|
public UiOptions() {
|
||||||
|
if ("true".equals(System.getProperty("debug"))) {
|
||||||
|
this.drawFps = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDrawFps() {
|
||||||
|
return this.drawFps;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDrawFps(final boolean drawFps) {
|
||||||
|
this.drawFps = drawFps;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBackgroundResourceName0() {
|
||||||
|
return this.backgroundResourceName0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBackgroundResourceName0(final String background0) {
|
||||||
|
this.backgroundResourceName0 = background0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isBackgroundScrolling0() {
|
||||||
|
return this.backgroundScrolling0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBackgroundScrolling0(final boolean backgroundScrolling0) {
|
||||||
|
this.backgroundScrolling0 = backgroundScrolling0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isBackgroundScrollingReverse0() {
|
||||||
|
return this.backgroundScrollingReverse0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBackgroundScrollingReverse0(final boolean backgroundScrollingReverse0) {
|
||||||
|
this.backgroundScrollingReverse0 = backgroundScrollingReverse0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isBackgroundScrolling1() {
|
||||||
|
return this.backgroundScrolling1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBackgroundScrolling1(final boolean backgroundScrolling1) {
|
||||||
|
this.backgroundScrolling1 = backgroundScrolling1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isBackgroundScrollingReverse1() {
|
||||||
|
return this.backgroundScrollingReverse1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBackgroundScrollingReverse1(final boolean backgroundScrollingReverse1) {
|
||||||
|
this.backgroundScrollingReverse1 = backgroundScrollingReverse1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CoordI2 getBackgroundOffset0() {
|
||||||
|
return this.backgroundOffset0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBackgroundOffset0(final CoordI2 backgroundOffset0) {
|
||||||
|
this.backgroundOffset0 = backgroundOffset0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CoordI2 getBackgroundOffset1() {
|
||||||
|
return this.backgroundOffset1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBackgroundOffset1(final CoordI2 backgroundOffset1) {
|
||||||
|
this.backgroundOffset1 = backgroundOffset1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBackgroundResourceModName0() {
|
||||||
|
return this.backgroundResourceModName0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBackgroundResourceModName0(final String backgroundResourceModName0) {
|
||||||
|
this.backgroundResourceModName0 = backgroundResourceModName0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||||
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||||
|
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package de.extio.game_engine.renderer.container;
|
||||||
|
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import de.extio.game_engine.renderer.RendererControl;
|
||||||
|
import de.extio.game_engine.renderer.RenderingBoPool;
|
||||||
|
import de.extio.game_engine.spatial2.model.Area2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to position, draw and manage virtual windows in the UI.
|
||||||
|
* EnhancedWindow holds a state to switch between different representations and also can persist the window position.
|
||||||
|
*/
|
||||||
|
public class EnhancedWindow<T> extends Window {
|
||||||
|
|
||||||
|
public final static String PERSISTENT_STATE_PREFIX = "ENHANCEDWINDOW_POSITION_";
|
||||||
|
|
||||||
|
private final boolean persistentPosition;
|
||||||
|
|
||||||
|
private T state;
|
||||||
|
|
||||||
|
private final T initialState;
|
||||||
|
|
||||||
|
private final Function<T, Area2> areaFunction;
|
||||||
|
|
||||||
|
public static void resetWindows() {
|
||||||
|
// boolean removed = false;
|
||||||
|
// final Iterator<String> it = EngineFacade.instance().getPersistentClientState().state.keySet().iterator();
|
||||||
|
// while (it.hasNext()) {
|
||||||
|
// final String key = it.next();
|
||||||
|
// if (key.startsWith(EnhancedWindow.PERSISTENT_STATE_PREFIX)) {
|
||||||
|
// it.remove();
|
||||||
|
// removed = true;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// if (removed) {
|
||||||
|
// EngineFacade.instance().storePersistentClientState();
|
||||||
|
// }
|
||||||
|
|
||||||
|
synchronized (WINDOWS) {
|
||||||
|
for (final Window window : WINDOWS) {
|
||||||
|
if (window instanceof EnhancedWindow) {
|
||||||
|
window.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public EnhancedWindow(final String name, final boolean draggable, final boolean persistentPosition, final T initialState, final RenderingBoPool renderingBoPool, final RendererControl rendererControl, final Function<T, Area2> areaFunction) {
|
||||||
|
super(name, draggable, renderingBoPool, rendererControl);
|
||||||
|
|
||||||
|
this.initialState = initialState;
|
||||||
|
this.areaFunction = areaFunction;
|
||||||
|
this.persistentPosition = persistentPosition;
|
||||||
|
|
||||||
|
this.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T getState() {
|
||||||
|
return this.state;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setState(final T state) {
|
||||||
|
if (state == null) {
|
||||||
|
this.reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.state = state;
|
||||||
|
}
|
||||||
|
if (this.areaFunction != null) {
|
||||||
|
this.updateArea();
|
||||||
|
this.savePosition();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateArea() {
|
||||||
|
if (this.areaFunction != null) {
|
||||||
|
final Area2 area = this.areaFunction.apply(this.state);
|
||||||
|
this.setDimension(area.getDimension());
|
||||||
|
// this.resizeCentered(area.getDimension());
|
||||||
|
if (!this.persistentPosition) {
|
||||||
|
this.setPosition(area.getPosition());
|
||||||
|
}
|
||||||
|
this.updateRelativeScreenPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
super.updateArea();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() {
|
||||||
|
super.reset();
|
||||||
|
this.state = this.initialState;
|
||||||
|
this.updateArea();
|
||||||
|
this.loadPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void releaseDragging() {
|
||||||
|
if (this.isDragging()) {
|
||||||
|
this.savePosition();
|
||||||
|
}
|
||||||
|
super.releaseDragging();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadPosition() {
|
||||||
|
if (this.persistentPosition && this.name != null) {
|
||||||
|
// final CoordD2 existingPosition = (CoordD2) EngineFacade.instance().getPersistentClientState().state.get(this.getPersistentStateKeyName());
|
||||||
|
// if (existingPosition != null) {
|
||||||
|
// LOGGER.debug(() -> "Position loaded: " + this.name + " " + existingPosition);
|
||||||
|
// this.relativeScreenPosition = MutableCoordD2.create(existingPosition);
|
||||||
|
// this.applyRelativeScreenPosition();
|
||||||
|
// }
|
||||||
|
/*else*/ if (this.areaFunction != null) {
|
||||||
|
final Area2 area = this.areaFunction.apply(this.state);
|
||||||
|
this.setPosition(area.getPosition());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void savePosition() {
|
||||||
|
if (!this.persistentPosition) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// final CoordD2 existingPosition = (CoordD2) EngineFacade.instance().getPersistentClientState().state.get(this.getPersistentStateKeyName());
|
||||||
|
// if (existingPosition == null || !this.relativeScreenPosition.equals(existingPosition)) {
|
||||||
|
// EngineFacade.instance().getPersistentClientState().state.put(this.getPersistentStateKeyName(), MutableCoordD2.create(this.relativeScreenPosition));
|
||||||
|
// EngineFacade.instance().storePersistentClientState();
|
||||||
|
// LOGGER.debug(() -> "Position stored: " + this.name + " " + this.getPosition());
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
// private String getPersistentStateKeyName() {
|
||||||
|
// return PERSISTENT_STATE_PREFIX.concat(this.name);
|
||||||
|
// }
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||||
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||||
|
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package de.extio.game_engine.renderer.container;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import de.extio.game_engine.renderer.RenderingBoPool;
|
||||||
|
import de.extio.game_engine.renderer.model.RenderingBo;
|
||||||
|
import de.extio.game_engine.renderer.model.RenderingBoLayer;
|
||||||
|
import de.extio.game_engine.renderer.model.RgbaColor;
|
||||||
|
import de.extio.game_engine.renderer.model.bo.ControlRenderingBo;
|
||||||
|
import de.extio.game_engine.renderer.model.bo.ControlRenderingBo.SliderControl;
|
||||||
|
import de.extio.game_engine.renderer.model.event.UiControlEvent;
|
||||||
|
import de.extio.game_engine.spatial2.model.Area2;
|
||||||
|
import de.extio.game_engine.spatial2.model.CoordI2;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper for scrollbars.
|
||||||
|
* I recommend to check for code examples to learn more about the usage of this helper and how to integrate in client modules.
|
||||||
|
*/
|
||||||
|
public class ScrollBar {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(ScrollBar.class);
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
|
||||||
|
private double scrollPosition;
|
||||||
|
|
||||||
|
private int lastNumElements;
|
||||||
|
|
||||||
|
private RenderingBoLayer layer = RenderingBoLayer.UI0_1;
|
||||||
|
|
||||||
|
public ScrollBar(final String nameSuffix) {
|
||||||
|
this.name = "Scrollbar_" + nameSuffix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onEvent(final UiControlEvent event, final Predicate<CoordI2> positionIntersectsPredicate) {
|
||||||
|
final UiControlEvent uiControlEvent = (UiControlEvent) event;
|
||||||
|
|
||||||
|
if (this.name.equals(uiControlEvent.getId())) {
|
||||||
|
this.scrollPosition = 1.0D - ((Double) uiControlEvent.getPayload()).doubleValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onMouseScroll(final int button, final CoordI2 absolutePos, final Predicate<CoordI2> positionIntersectsPredicate) {
|
||||||
|
if ((button == 4 || button == 5) &&
|
||||||
|
this.lastNumElements > 0 &&
|
||||||
|
positionIntersectsPredicate.test(absolutePos)) {
|
||||||
|
|
||||||
|
final double perElem = (button == 4 ? -1.0 : 1.0) / this.lastNumElements;
|
||||||
|
this.scrollPosition = Math.min(1.0, Math.max(0.0, this.scrollPosition + perElem));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void render(final List<RenderingBo> renderingBo, final Area2 renderingArea, final int numElements, final RenderingBoPool renderingBoPool) {
|
||||||
|
this.lastNumElements = numElements;
|
||||||
|
|
||||||
|
final RenderingBo bo = renderingBoPool.acquire(ControlRenderingBo.class)
|
||||||
|
.setId(this.name)
|
||||||
|
.setType(SliderControl.class)
|
||||||
|
.setCustomData(Boolean.FALSE)
|
||||||
|
.setCustomData2(Double.valueOf(1.0D - this.scrollPosition))
|
||||||
|
.setCustomData3(Double.valueOf(1.0D - this.scrollPosition))
|
||||||
|
.setEnabled(numElements > 0)
|
||||||
|
.setVisible(true)
|
||||||
|
.withDimensionAbsolute(20, renderingArea.getDimension().getY())
|
||||||
|
.withPositionAbsoluteAnchorTopLeft(renderingArea.getPosition().getX() + renderingArea.getDimension().getX() - 20, renderingArea.getPosition().getY())
|
||||||
|
.setColor(RgbaColor.LIGHT_GRAY)
|
||||||
|
.setLayer(this.layer);
|
||||||
|
renderingBo.add(bo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getScrollPosition() {
|
||||||
|
return this.scrollPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScrollPosition(final double scrollPosition) {
|
||||||
|
this.scrollPosition = scrollPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RenderingBoLayer getLayer() {
|
||||||
|
return this.layer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLayer(final RenderingBoLayer layer) {
|
||||||
|
this.layer = layer;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,370 @@
|
|||||||
|
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||||
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||||
|
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package de.extio.game_engine.renderer.container;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import de.extio.game_engine.renderer.RendererControl;
|
||||||
|
import de.extio.game_engine.renderer.RenderingBoPool;
|
||||||
|
import de.extio.game_engine.renderer.model.RenderingBo;
|
||||||
|
import de.extio.game_engine.renderer.model.RenderingBoLayer;
|
||||||
|
import de.extio.game_engine.renderer.model.RgbaColor;
|
||||||
|
import de.extio.game_engine.renderer.model.bo.ControlRenderingBo;
|
||||||
|
import de.extio.game_engine.renderer.model.bo.DrawWindowRenderingBo;
|
||||||
|
import de.extio.game_engine.renderer.model.bo.ControlRenderingBo.SliderControl;
|
||||||
|
import de.extio.game_engine.renderer.model.bo.ControlRenderingBo.WindowCloseButtonControl;
|
||||||
|
import de.extio.game_engine.renderer.model.event.UiControlEvent;
|
||||||
|
import de.extio.game_engine.spatial2.WorldUtils2;
|
||||||
|
import de.extio.game_engine.spatial2.model.Area2;
|
||||||
|
import de.extio.game_engine.spatial2.model.CoordD2;
|
||||||
|
import de.extio.game_engine.spatial2.model.CoordI2;
|
||||||
|
import de.extio.game_engine.spatial2.model.HasPositionAndDimension2;
|
||||||
|
import de.extio.game_engine.spatial2.model.ImmutableCoordI2;
|
||||||
|
import de.extio.game_engine.spatial2.model.MutableCoordD2;
|
||||||
|
import de.extio.game_engine.spatial2.model.MutableCoordI2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to position, draw and manage virtual windows in the UI.
|
||||||
|
*/
|
||||||
|
public class Window implements HasPositionAndDimension2, AutoCloseable {
|
||||||
|
|
||||||
|
protected final static Logger LOGGER = LoggerFactory.getLogger(Window.class);
|
||||||
|
|
||||||
|
protected final static List<Window> WINDOWS = Collections.synchronizedList(new ArrayList<>());
|
||||||
|
|
||||||
|
protected CoordD2 relativeScreenPosition = MutableCoordD2.create();
|
||||||
|
|
||||||
|
protected final String name;
|
||||||
|
|
||||||
|
private final RenderingBoPool renderingBoPool;
|
||||||
|
|
||||||
|
private final RendererControl rendererControl;
|
||||||
|
|
||||||
|
private Area2 area;
|
||||||
|
|
||||||
|
private CoordI2 dragCoordPrev;
|
||||||
|
|
||||||
|
private boolean dragging;
|
||||||
|
|
||||||
|
private boolean draggable;
|
||||||
|
|
||||||
|
private boolean drawCloseButton;
|
||||||
|
|
||||||
|
private RenderingBoLayer renderingBoLayer;
|
||||||
|
|
||||||
|
private CoordI2 lastEffectiveViewportDimension;
|
||||||
|
|
||||||
|
private RgbaColor color;
|
||||||
|
|
||||||
|
private long lastFrameDisplayed;
|
||||||
|
|
||||||
|
public Window(final String name, final boolean draggable, final RenderingBoPool renderingBoPool, final RendererControl rendererControl) {
|
||||||
|
this.name = name;
|
||||||
|
this.draggable = draggable;
|
||||||
|
this.renderingBoPool = renderingBoPool;
|
||||||
|
this.rendererControl = rendererControl;
|
||||||
|
|
||||||
|
WINDOWS.add(this);
|
||||||
|
this.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean intersectsAny(final HasPositionAndDimension2 area) {
|
||||||
|
// final long frame = EngineFacade.instance().getFrame();
|
||||||
|
// synchronized (WINDOWS) {
|
||||||
|
// for (final Window window : WINDOWS) {
|
||||||
|
// if (window.lastFrameDisplayed >= frame - 1 && WorldUtils.intersects(window, area)) {
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return false;
|
||||||
|
synchronized (WINDOWS) {
|
||||||
|
for (final Window window : WINDOWS) {
|
||||||
|
if (WorldUtils2.intersects(window, area)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset() {
|
||||||
|
this.area = new Area2();
|
||||||
|
this.area.setPosition(MutableCoordI2.create());
|
||||||
|
this.area.setDimension(MutableCoordI2.create());
|
||||||
|
this.releaseDragging();
|
||||||
|
this.updateRelativeScreenPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void releaseDragging() {
|
||||||
|
this.dragCoordPrev = null;
|
||||||
|
this.dragging = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean drag(final CoordI2 coord) {
|
||||||
|
if (!this.draggable) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (this.dragCoordPrev != null) {
|
||||||
|
if (!this.dragging && this.intersects(coord)) {
|
||||||
|
synchronized (WINDOWS) {
|
||||||
|
for (final Window window : WINDOWS) {
|
||||||
|
if (window.isDragging()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dragging = true;
|
||||||
|
}
|
||||||
|
if (this.dragging) {
|
||||||
|
this.move(MutableCoordI2.create(coord).substract(this.dragCoordPrev));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
this.dragCoordPrev = coord;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.intersects(coord);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void move(final CoordI2 delta) {
|
||||||
|
this.area.getPosition().add(delta);
|
||||||
|
this.ensureBounds();
|
||||||
|
this.updateRelativeScreenPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateArea() {
|
||||||
|
this.applyRelativeScreenPosition();
|
||||||
|
this.ensureBounds();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resizeCentered(final CoordI2 dimension) {
|
||||||
|
if (ImmutableCoordI2.zero().equals(this.area.getDimension())) {
|
||||||
|
this.area.getDimension().setXY(dimension);
|
||||||
|
this.updateRelativeScreenPosition();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final CoordI2 delta = MutableCoordI2.create(this.area.getDimension()).substract(dimension);
|
||||||
|
if (ImmutableCoordI2.zero().equals(delta)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.area.getDimension().setXY(dimension);
|
||||||
|
this.move(MutableCoordI2.create(delta.getX() / 2, delta.getY() / 2));
|
||||||
|
this.updateRelativeScreenPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean intersects(final CoordI2 coord) {
|
||||||
|
return WorldUtils2.intersects(this.area.getPosition(), this.area.getDimension(), coord, ImmutableCoordI2.one());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void render(final List<RenderingBo> renderingBo, final BiConsumer<List<RenderingBo>, RenderingBo> consumer) {
|
||||||
|
final CoordI2 currentEffectiveViewportDimension = rendererControl.getEffectiveViewportDimension();
|
||||||
|
if (!currentEffectiveViewportDimension.equals(this.lastEffectiveViewportDimension)) {
|
||||||
|
this.lastEffectiveViewportDimension = currentEffectiveViewportDimension;
|
||||||
|
this.updateArea();
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderingBo bo = renderingBoPool.acquire(DrawWindowRenderingBo.class)
|
||||||
|
.setThickBorder(this.draggable)
|
||||||
|
.withDimensionAbsolute(this.area.getDimension())
|
||||||
|
.withPositionAbsoluteAnchorTopLeft(this.area.getPosition())
|
||||||
|
.setColor(this.color != null ? this.color : null);
|
||||||
|
if (this.renderingBoLayer != null) {
|
||||||
|
bo.setLayer(this.renderingBoLayer);
|
||||||
|
}
|
||||||
|
consumer.accept(renderingBo, bo);
|
||||||
|
|
||||||
|
if (this.drawCloseButton) {
|
||||||
|
bo = renderingBoPool.acquire(ControlRenderingBo.class)
|
||||||
|
.setType(WindowCloseButtonControl.class)
|
||||||
|
.setId(this.name + "_Close")
|
||||||
|
.setCustomData(this.draggable)
|
||||||
|
.setEnabled(true)
|
||||||
|
.setVisible(true)
|
||||||
|
.withDimensionAbsolute(this.area.getDimension())
|
||||||
|
.withPositionAbsoluteAnchorTopLeft(this.area.getPosition())
|
||||||
|
.setColor(this.color != null ? this.color : null);
|
||||||
|
if (this.renderingBoLayer != null) {
|
||||||
|
bo.setLayer(RenderingBoLayer.values()[Math.min(this.renderingBoLayer.ordinal() + 2, RenderingBoLayer.values().length - 1)]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
bo.setLayer(RenderingBoLayer.UI0_1);
|
||||||
|
}
|
||||||
|
consumer.accept(renderingBo, bo);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lastFrameDisplayed = this.rendererControl.getFrame();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
this.releaseDragging();
|
||||||
|
WINDOWS.remove(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void ensureBounds() {
|
||||||
|
final CoordI2 effectiveDim = this.rendererControl.getEffectiveViewportDimension();
|
||||||
|
boolean updated = false;
|
||||||
|
|
||||||
|
if (this.area.getPosition().getX() < 0) {
|
||||||
|
this.area.getPosition().setX(0);
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
if (this.area.getPosition().getY() < 0) {
|
||||||
|
this.area.getPosition().setY(0);
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
if (this.area.getDimension().getX() > effectiveDim.getX()) {
|
||||||
|
this.area.getDimension().setX(effectiveDim.getX());
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
if (this.area.getDimension().getY() > effectiveDim.getY()) {
|
||||||
|
this.area.getDimension().setY(effectiveDim.getY());
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
if (this.area.getPosition().getX() + this.area.getDimension().getX() > effectiveDim.getX()) {
|
||||||
|
this.area.getPosition().setX(effectiveDim.getX() - this.area.getDimension().getX());
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
if (this.area.getPosition().getY() + this.area.getDimension().getY() > effectiveDim.getY()) {
|
||||||
|
this.area.getPosition().setY(effectiveDim.getY() - this.area.getDimension().getY());
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updated) {
|
||||||
|
this.updateRelativeScreenPosition();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void updateRelativeScreenPosition() {
|
||||||
|
final CoordI2 currentEffectiveViewportDimension = this.rendererControl.getEffectiveViewportDimension();
|
||||||
|
this.relativeScreenPosition.setX((double) this.area.getPosition().getX() / (double) currentEffectiveViewportDimension.getX());
|
||||||
|
this.relativeScreenPosition.setY((double) this.area.getPosition().getY() / (double) currentEffectiveViewportDimension.getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void applyRelativeScreenPosition() {
|
||||||
|
final CoordI2 currentEffectiveViewportDimension = this.rendererControl.getEffectiveViewportDimension();
|
||||||
|
this.area.getPosition().setX((int) (this.relativeScreenPosition.getX() * (double) currentEffectiveViewportDimension.getX()));
|
||||||
|
this.area.getPosition().setY((int) (this.relativeScreenPosition.getY() * (double) currentEffectiveViewportDimension.getY()));
|
||||||
|
this.ensureBounds();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getX() {
|
||||||
|
return this.area.getPosition().getX();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setX(final int x) {
|
||||||
|
this.area.getPosition().setX(x);
|
||||||
|
this.ensureBounds();
|
||||||
|
this.updateRelativeScreenPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getY() {
|
||||||
|
return this.area.getPosition().getY();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setY(final int y) {
|
||||||
|
this.area.getPosition().setY(y);
|
||||||
|
this.ensureBounds();
|
||||||
|
this.updateRelativeScreenPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getWidth() {
|
||||||
|
return this.area.getDimension().getX();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWidth(final int width) {
|
||||||
|
this.area.getDimension().setX(width);
|
||||||
|
this.ensureBounds();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getHeight() {
|
||||||
|
return this.area.getDimension().getY();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHeight(final int height) {
|
||||||
|
this.area.getDimension().setY(height);
|
||||||
|
this.ensureBounds();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CoordI2 getPosition() {
|
||||||
|
return this.area.getPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPosition(final CoordI2 position) {
|
||||||
|
this.area.getPosition().setXY(position);
|
||||||
|
this.ensureBounds();
|
||||||
|
this.updateRelativeScreenPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CoordI2 getDimension() {
|
||||||
|
return ImmutableCoordI2.create(this.area.getDimension());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDimension(final CoordI2 dimension) {
|
||||||
|
this.area.getDimension().setXY(dimension);
|
||||||
|
this.ensureBounds();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Area2 getArea() {
|
||||||
|
return new Area2(ImmutableCoordI2.create(this.area.getPosition()), ImmutableCoordI2.create(this.area.getDimension()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDragging() {
|
||||||
|
return this.dragging;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDraggable() {
|
||||||
|
return this.draggable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDraggable(final boolean draggable) {
|
||||||
|
this.draggable = draggable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RenderingBoLayer getRenderingBoLayer() {
|
||||||
|
return this.renderingBoLayer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRenderingBoLayer(final RenderingBoLayer renderingBoLayer) {
|
||||||
|
this.renderingBoLayer = renderingBoLayer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RgbaColor getColor() {
|
||||||
|
return this.color;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColor(final RgbaColor color) {
|
||||||
|
this.color = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDrawCloseButton() {
|
||||||
|
return this.drawCloseButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDrawCloseButton(final boolean drawCloseButton) {
|
||||||
|
this.drawCloseButton = drawCloseButton;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,435 @@
|
|||||||
|
package de.extio.game_engine.renderer.g2d;
|
||||||
|
|
||||||
|
import java.awt.AWTEvent;
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.DisplayMode;
|
||||||
|
import java.awt.EventQueue;
|
||||||
|
import java.awt.Frame;
|
||||||
|
import java.awt.Graphics;
|
||||||
|
import java.awt.GraphicsDevice;
|
||||||
|
import java.awt.GraphicsEnvironment;
|
||||||
|
import java.awt.Point;
|
||||||
|
import java.awt.Rectangle;
|
||||||
|
import java.awt.Toolkit;
|
||||||
|
import java.awt.Window;
|
||||||
|
import java.awt.event.AWTEventListener;
|
||||||
|
import java.awt.event.ComponentEvent;
|
||||||
|
import java.awt.event.ComponentListener;
|
||||||
|
import java.awt.event.InputEvent;
|
||||||
|
import java.awt.event.KeyEvent;
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.awt.event.MouseListener;
|
||||||
|
import java.awt.event.MouseMotionListener;
|
||||||
|
import java.awt.event.MouseWheelEvent;
|
||||||
|
import java.awt.event.MouseWheelListener;
|
||||||
|
import java.awt.event.WindowEvent;
|
||||||
|
import java.awt.event.WindowListener;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import de.extio.game_engine.spatial2.model.CoordI2;
|
||||||
|
import de.extio.game_engine.spatial2.model.ImmutableCoordI2;
|
||||||
|
import de.extio.game_engine.keyboard.KeyModifiers;
|
||||||
|
import de.extio.game_engine.renderer.RendererControl;
|
||||||
|
import de.extio.game_engine.renderer.RendererData;
|
||||||
|
import de.extio.game_engine.renderer.g2d.control.G2DControlHasExclusiveKeyEvent;
|
||||||
|
import de.extio.game_engine.renderer.model.event.KeyStrokeEvent;
|
||||||
|
import de.extio.game_engine.renderer.model.event.MouseClickEvent;
|
||||||
|
import de.extio.game_engine.renderer.model.event.MouseEnterEvent;
|
||||||
|
import de.extio.game_engine.renderer.model.event.MouseMoveEvent;
|
||||||
|
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
public class G2DMainFrame extends Frame {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(G2DMainFrame.class);
|
||||||
|
|
||||||
|
private final RendererData rendererData;
|
||||||
|
|
||||||
|
private AWTEventListener aWTEventListener;
|
||||||
|
|
||||||
|
private volatile int fullScreenIdxRegistered = -1;
|
||||||
|
|
||||||
|
public volatile boolean resizeListenerEnabled;
|
||||||
|
|
||||||
|
public G2DMainFrame(final RendererData rendererData) {
|
||||||
|
LOGGER.debug("ctor");
|
||||||
|
|
||||||
|
this.rendererData = rendererData;
|
||||||
|
this.setIgnoreRepaint(true);
|
||||||
|
this.setTitle(this.getClass().getCanonicalName());
|
||||||
|
this.setLayout(null);
|
||||||
|
this.setBackground(Color.BLACK);
|
||||||
|
|
||||||
|
try (InputStream in = this.getClass().getClassLoader().getResourceAsStream("icon.png")) {
|
||||||
|
final BufferedImage icon = ImageIO.read(in);
|
||||||
|
this.setIconImage(icon);
|
||||||
|
}
|
||||||
|
catch (final IOException e) {
|
||||||
|
LOGGER.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.addWindowListener(new WindowListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void windowOpened(final WindowEvent e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void windowIconified(final WindowEvent e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void windowDeiconified(final WindowEvent e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void windowDeactivated(final WindowEvent e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void windowClosing(final WindowEvent e) {
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void windowClosed(final WindowEvent e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void windowActivated(final WindowEvent e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.addComponentListener(new ComponentListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void componentShown(final ComponentEvent e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void componentResized(final ComponentEvent e) {
|
||||||
|
if (G2DMainFrame.this.resizeListenerEnabled) {
|
||||||
|
((G2DRendererControl) G2DMainFrame.this.rendererData.getRendererControl()).updateViewPort(true, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void componentMoved(final ComponentEvent e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void componentHidden(final ComponentEvent e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.addMouseListener(new MouseListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseReleased(final MouseEvent e) {
|
||||||
|
try {
|
||||||
|
G2DMainFrame.this.rendererData.getEventConsumer().accept(new MouseClickEvent(false, G2DMainFrame.getModifiers(e), e.getButton(), ImmutableCoordI2.create(e.getX(), e.getY())));
|
||||||
|
}
|
||||||
|
catch (final Exception exc) {
|
||||||
|
LOGGER.warn(exc.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mousePressed(final MouseEvent e) {
|
||||||
|
try {
|
||||||
|
G2DMainFrame.this.requestFocus();
|
||||||
|
G2DMainFrame.this.rendererData.getEventConsumer().accept(new MouseClickEvent(true, G2DMainFrame.getModifiers(e), e.getButton(), ImmutableCoordI2.create(e.getX(), e.getY())));
|
||||||
|
}
|
||||||
|
catch (final Exception exc) {
|
||||||
|
LOGGER.warn(exc.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseExited(final MouseEvent e) {
|
||||||
|
try {
|
||||||
|
G2DMainFrame.this.rendererData.getEventConsumer().accept(new MouseMoveEvent(false, G2DMainFrame.getModifiers(e), null, 0));
|
||||||
|
}
|
||||||
|
catch (final Exception exc) {
|
||||||
|
LOGGER.warn(exc.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseEntered(final MouseEvent e) {
|
||||||
|
try {
|
||||||
|
G2DMainFrame.this.rendererData.getEventConsumer().accept(new MouseEnterEvent(G2DMainFrame.getModifiers(e), ImmutableCoordI2.create(e.getX(), e.getY())));
|
||||||
|
}
|
||||||
|
catch (final Exception exc) {
|
||||||
|
LOGGER.warn(exc.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseClicked(final MouseEvent e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.addMouseMotionListener(new MouseMotionListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseMoved(final MouseEvent e) {
|
||||||
|
final int button = this.getButton(e);
|
||||||
|
|
||||||
|
try {
|
||||||
|
G2DMainFrame.this.rendererData.getEventConsumer().accept(new MouseMoveEvent(false, G2DMainFrame.getModifiers(e), ImmutableCoordI2.create(e.getX(), e.getY()), button));
|
||||||
|
}
|
||||||
|
catch (final Exception exc) {
|
||||||
|
if ("Engine not started up yet".equals(exc.getMessage())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LOGGER.error(exc.getMessage(), exc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseDragged(final MouseEvent e) {
|
||||||
|
final int button = this.getButton(e);
|
||||||
|
|
||||||
|
try {
|
||||||
|
G2DMainFrame.this.rendererData.getEventConsumer().accept(new MouseMoveEvent(true, G2DMainFrame.getModifiers(e), ImmutableCoordI2.create(e.getX(), e.getY()), button));
|
||||||
|
}
|
||||||
|
catch (final Exception exc) {
|
||||||
|
if ("Engine not started up yet".equals(exc.getMessage())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LOGGER.error(exc.getMessage(), exc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getButton(final MouseEvent e) {
|
||||||
|
int button = MouseEvent.NOBUTTON;
|
||||||
|
if ((e.getModifiersEx() & InputEvent.BUTTON1_DOWN_MASK) != 0) {
|
||||||
|
button = MouseEvent.BUTTON1;
|
||||||
|
}
|
||||||
|
else if ((e.getModifiersEx() & InputEvent.BUTTON2_DOWN_MASK) != 0) {
|
||||||
|
button = MouseEvent.BUTTON2;
|
||||||
|
}
|
||||||
|
else if ((e.getModifiersEx() & InputEvent.BUTTON3_DOWN_MASK) != 0) {
|
||||||
|
button = MouseEvent.BUTTON3;
|
||||||
|
}
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.addMouseWheelListener(new MouseWheelListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseWheelMoved(final MouseWheelEvent e) {
|
||||||
|
try {
|
||||||
|
G2DMainFrame.this.rendererData.getEventConsumer().accept(new MouseClickEvent(true, G2DMainFrame.getModifiers(e), e.getWheelRotation() == -1 ? 4 : 5, ImmutableCoordI2.create(e.getX(), e.getY())));
|
||||||
|
}
|
||||||
|
catch (final Exception exc) {
|
||||||
|
LOGGER.warn(exc.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.aWTEventListener = new AWTEventListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void eventDispatched(final AWTEvent event) {
|
||||||
|
if (!(event instanceof KeyEvent)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final KeyEvent e = (KeyEvent) event;
|
||||||
|
if (e.getComponent() instanceof G2DControlHasExclusiveKeyEvent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.getID() == KeyEvent.KEY_PRESSED) {
|
||||||
|
LOGGER.trace("Press {} {} {}", e.getKeyCode(), KeyEvent.getKeyText(e.getKeyCode()), G2DMainFrame.getModifiers(e));
|
||||||
|
final KeyStrokeEvent keyPressMessage = new KeyStrokeEvent(false, e.getKeyCode(), KeyEvent.getKeyText(e.getKeyCode()), G2DMainFrame.getModifiers(e));
|
||||||
|
G2DMainFrame.this.rendererData.getEventConsumer().accept(keyPressMessage);
|
||||||
|
}
|
||||||
|
else if (event.getID() == KeyEvent.KEY_RELEASED) {
|
||||||
|
LOGGER.trace("Release {} {} {}", e.getKeyCode(), KeyEvent.getKeyText(e.getKeyCode()), G2DMainFrame.getModifiers(e));
|
||||||
|
final KeyStrokeEvent keyPressMessage = new KeyStrokeEvent(true, e.getKeyCode(), KeyEvent.getKeyText(e.getKeyCode()), G2DMainFrame.getModifiers(e));
|
||||||
|
G2DMainFrame.this.rendererData.getEventConsumer().accept(keyPressMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Toolkit.getDefaultToolkit().addAWTEventListener(this.aWTEventListener, AWTEvent.KEY_EVENT_MASK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(final Graphics g) {
|
||||||
|
// The standard update() method fills background of sub-components with bgr color - I don't want that!
|
||||||
|
|
||||||
|
// Now that all components are cached light weights, I don't need AWT updates at all
|
||||||
|
// if (this.iSSHOWING()) {
|
||||||
|
// THIS.PAINT(G);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose() {
|
||||||
|
Toolkit.getDefaultToolkit().removeAWTEventListener(this.aWTEventListener);
|
||||||
|
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void windowedPre() {
|
||||||
|
LOGGER.debug("windowedPre");
|
||||||
|
|
||||||
|
this.setSize(795, 596);
|
||||||
|
this.setVisible(true);
|
||||||
|
Toolkit.getDefaultToolkit().sync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void windowed() {
|
||||||
|
LOGGER.debug("windowed");
|
||||||
|
|
||||||
|
this.unregisterFullScreenWindow();
|
||||||
|
this.setUndecorated(false);
|
||||||
|
this.setAlwaysOnTop(false);
|
||||||
|
|
||||||
|
this.applyReferenceWindowGeometry();
|
||||||
|
this.setVisible(true);
|
||||||
|
Toolkit.getDefaultToolkit().sync();
|
||||||
|
|
||||||
|
EventQueue.invokeLater(() -> {
|
||||||
|
this.toFront();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void fullscreen() {
|
||||||
|
LOGGER.debug("windowedFull {}", this.rendererData.getVideoOptions().getFullScreenNumber());
|
||||||
|
|
||||||
|
this.setUndecorated(true);
|
||||||
|
this.setAlwaysOnTop(true);
|
||||||
|
|
||||||
|
this.unregisterFullScreenWindow();
|
||||||
|
this.registerFullScreenWindow(this.rendererData.getVideoOptions().getFullScreenNumber(), this);
|
||||||
|
Toolkit.getDefaultToolkit().sync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registerFullScreenWindow(final int screenIdx, final Window window) {
|
||||||
|
final GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
|
||||||
|
GraphicsDevice device;
|
||||||
|
if (screenIdx > -1 && screenIdx < env.getScreenDevices().length) {
|
||||||
|
device = env.getScreenDevices()[screenIdx];
|
||||||
|
this.fullScreenIdxRegistered = window != null ? screenIdx : -1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
device = env.getDefaultScreenDevice();
|
||||||
|
for (int i = 0; i < env.getScreenDevices().length; i++) {
|
||||||
|
if (device.getIDstring().equals(env.getScreenDevices()[i].getIDstring())) {
|
||||||
|
this.rendererData.getVideoOptions().setFullScreenNumber(i);
|
||||||
|
this.fullScreenIdxRegistered = window != null ? screenIdx : -1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window != null) {
|
||||||
|
final int x = (int) device.getDefaultConfiguration().getBounds().getWidth();
|
||||||
|
final int y = (int) device.getDefaultConfiguration().getBounds().getHeight();
|
||||||
|
LOGGER.debug("Full screen size: {}x{}", x, y);
|
||||||
|
window.setSize(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
device.setFullScreenWindow(window);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unregisterFullScreenWindow() {
|
||||||
|
if (this.fullScreenIdxRegistered > -1) {
|
||||||
|
LOGGER.debug("Unregister full screen window {}", this.fullScreenIdxRegistered);
|
||||||
|
this.registerFullScreenWindow(this.fullScreenIdxRegistered, null);
|
||||||
|
this.fullScreenIdxRegistered = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void borderless() {
|
||||||
|
LOGGER.debug("borderless {}", this.rendererData.getVideoOptions().getFullScreenNumber());
|
||||||
|
|
||||||
|
this.unregisterFullScreenWindow();
|
||||||
|
|
||||||
|
this.setUndecorated(true);
|
||||||
|
|
||||||
|
this.setupBorderlessWindow(this.rendererData.getVideoOptions().getFullScreenNumber());
|
||||||
|
Toolkit.getDefaultToolkit().sync();
|
||||||
|
|
||||||
|
this.setAlwaysOnTop(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupBorderlessWindow(final int screenIdx) {
|
||||||
|
final GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
|
||||||
|
GraphicsDevice device;
|
||||||
|
if (screenIdx > -1 && screenIdx < env.getScreenDevices().length) {
|
||||||
|
device = env.getScreenDevices()[screenIdx];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
device = env.getDefaultScreenDevice();
|
||||||
|
for (int i = 0; i < env.getScreenDevices().length; i++) {
|
||||||
|
if (device.getIDstring().equals(env.getScreenDevices()[i].getIDstring())) {
|
||||||
|
this.rendererData.getVideoOptions().setFullScreenNumber(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final Point location = device.getDefaultConfiguration().getBounds().getLocation();
|
||||||
|
LOGGER.debug("Borderless screen location: {}", location);
|
||||||
|
this.setLocation(location);
|
||||||
|
final Rectangle bounds = device.getDefaultConfiguration().getBounds();
|
||||||
|
LOGGER.debug("Borderless screen size: {}", bounds);
|
||||||
|
this.setSize(bounds.width, bounds.height);
|
||||||
|
|
||||||
|
this.setVisible(true);
|
||||||
|
// this.setExtendedState(Frame.MAXIMIZED_BOTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void createBufferStrategy() {
|
||||||
|
this.createBufferStrategy(2);
|
||||||
|
// Toolkit.getDefaultToolkit().sync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyReferenceWindowGeometry() {
|
||||||
|
final DisplayMode mode = this.getGraphicsConfiguration().getDevice().getDisplayMode();
|
||||||
|
final CoordI2 size = ImmutableCoordI2.create(Math.min(mode.getWidth(), RendererControl.REFERENCE_RESOLUTION.getX()), Math.min(mode.getHeight(), RendererControl.REFERENCE_RESOLUTION.getY()));
|
||||||
|
this.setLocation(mode.getWidth() / 2 - size.getX() / 2, mode.getHeight() / 2 - size.getY() / 2);
|
||||||
|
this.setSize(size.getX(), size.getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getModifiers(final InputEvent event) {
|
||||||
|
int result = 0;
|
||||||
|
|
||||||
|
if (event.isShiftDown()) {
|
||||||
|
result |= KeyModifiers.MODIFIER_SHIFT;
|
||||||
|
}
|
||||||
|
if (event.isAltDown()) {
|
||||||
|
result |= KeyModifiers.MODIFIER_ALT;
|
||||||
|
}
|
||||||
|
if (event.isAltGraphDown()) {
|
||||||
|
result |= KeyModifiers.MODIFIER_ALTGR;
|
||||||
|
}
|
||||||
|
if (event.isControlDown()) {
|
||||||
|
result |= KeyModifiers.MODIFIER_CTRL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
329
src/main/java/de/extio/game_engine/renderer/g2d/G2DRenderer.java
Normal file
329
src/main/java/de/extio/game_engine/renderer/g2d/G2DRenderer.java
Normal file
@@ -0,0 +1,329 @@
|
|||||||
|
package de.extio.game_engine.renderer.g2d;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.EventQueue;
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.Toolkit;
|
||||||
|
import java.awt.image.BufferStrategy;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Semaphore;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import de.extio.game_engine.renderer.Renderer;
|
||||||
|
import de.extio.game_engine.renderer.RenderingBoPoolImpl;
|
||||||
|
import de.extio.game_engine.renderer.g2d.control.G2DDrawControl;
|
||||||
|
import de.extio.game_engine.renderer.g2d.control.G2DDrawControlTooltip;
|
||||||
|
import de.extio.game_engine.renderer.g2d.bo.rendering.G2DAbstractRenderingBo;
|
||||||
|
import de.extio.game_engine.renderer.g2d.bo.rendering.G2DDrawBackground;
|
||||||
|
import de.extio.game_engine.renderer.g2d.bo.rendering.G2DDrawEffect;
|
||||||
|
import de.extio.game_engine.renderer.g2d.bo.rendering.G2DDrawFont;
|
||||||
|
import de.extio.game_engine.renderer.g2d.bo.rendering.G2DDrawFpsHistory;
|
||||||
|
import de.extio.game_engine.renderer.g2d.bo.rendering.G2DDrawImage;
|
||||||
|
import de.extio.game_engine.renderer.g2d.bo.rendering.G2DDrawTest;
|
||||||
|
import de.extio.game_engine.renderer.g2d.bo.rendering.G2DDrawWindow;
|
||||||
|
import de.extio.game_engine.renderer.RendererData;
|
||||||
|
import de.extio.game_engine.renderer.model.bo.ControlRenderingBo;
|
||||||
|
import de.extio.game_engine.renderer.model.bo.DrawEffectRenderingBo;
|
||||||
|
import de.extio.game_engine.renderer.model.bo.DrawFontRenderingBo;
|
||||||
|
import de.extio.game_engine.renderer.model.bo.DrawImageRenderingBo;
|
||||||
|
import de.extio.game_engine.renderer.model.bo.DrawTestRenderingBo;
|
||||||
|
import de.extio.game_engine.renderer.model.bo.DrawWindowRenderingBo;
|
||||||
|
import de.extio.game_engine.renderer.model.event.TakeScreenshotEvent;
|
||||||
|
import de.extio.game_engine.renderer.model.RenderingBo;
|
||||||
|
import de.extio.game_engine.renderer.model.RenderingBoLayer;
|
||||||
|
import de.extio.game_engine.renderer.model.RgbaColor;
|
||||||
|
import de.extio.game_engine.spatial2.model.CoordI2;
|
||||||
|
import de.extio.game_engine.util.RingBuffer;
|
||||||
|
|
||||||
|
public class G2DRenderer implements Renderer {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(G2DRenderer.class);
|
||||||
|
|
||||||
|
private static final long SLEEP_PRECISION = TimeUnit.MILLISECONDS.toNanos(3);
|
||||||
|
|
||||||
|
private volatile G2DMainFrame mainFrame;
|
||||||
|
|
||||||
|
private final RendererData rendererData;
|
||||||
|
|
||||||
|
private final RingBuffer<Integer> fpsHistory = new RingBuffer<>(90);
|
||||||
|
|
||||||
|
private long fpsMeasurement;
|
||||||
|
|
||||||
|
private int fpsCur;
|
||||||
|
|
||||||
|
private int fps;
|
||||||
|
|
||||||
|
private long frameStart;
|
||||||
|
|
||||||
|
private long frameDur;
|
||||||
|
|
||||||
|
private final Semaphore semaphore = new Semaphore(1);
|
||||||
|
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
private volatile boolean takeScreenshot;
|
||||||
|
|
||||||
|
public G2DRenderer(final RendererData rendererData) {
|
||||||
|
LOGGER.info("ctor");
|
||||||
|
|
||||||
|
System.setProperty("sun.java2d.uiScale", "1");
|
||||||
|
System.setProperty("awt.nativeDoubleBuffering", "true");
|
||||||
|
System.setProperty("awt.useSystemAAFontSettings", "on");
|
||||||
|
System.setProperty("swing.aatext", "true");
|
||||||
|
|
||||||
|
this.rendererData = rendererData;
|
||||||
|
|
||||||
|
// TODO: Discover with spring dm
|
||||||
|
RenderingBoPoolImpl.INTERFACE_TO_BO_G2D.put(DrawFontRenderingBo.class, G2DDrawFont.class);
|
||||||
|
RenderingBoPoolImpl.INTERFACE_TO_BO_G2D.put(DrawImageRenderingBo.class, G2DDrawImage.class);
|
||||||
|
RenderingBoPoolImpl.INTERFACE_TO_BO_G2D.put(ControlRenderingBo.class, G2DDrawControl.class);
|
||||||
|
RenderingBoPoolImpl.INTERFACE_TO_BO_G2D.put(DrawWindowRenderingBo.class, G2DDrawWindow.class);
|
||||||
|
RenderingBoPoolImpl.INTERFACE_TO_BO_G2D.put(DrawTestRenderingBo.class, G2DDrawTest.class);
|
||||||
|
RenderingBoPoolImpl.INTERFACE_TO_BO_G2D.put(DrawEffectRenderingBo.class, G2DDrawEffect.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void show() {
|
||||||
|
try {
|
||||||
|
EventQueue.invokeAndWait(() -> {
|
||||||
|
this.rendererData.getRendererControl().applyVideoOptions();
|
||||||
|
});
|
||||||
|
EventQueue.invokeAndWait(() -> {
|
||||||
|
// this invokeAndWait() call is also important to wait until applyVideoOptions() has been completed, which also puts events to AWT event queue
|
||||||
|
LOGGER.debug("Viewport initialized");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (InvocationTargetException | InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void takeScreenshot() {
|
||||||
|
this.takeScreenshot = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() {
|
||||||
|
G2DDrawControl.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run(final List<RenderingBo> renderingBOs) throws InterruptedException {
|
||||||
|
try {
|
||||||
|
this.semaphore.acquire();
|
||||||
|
try {
|
||||||
|
if (this.mainFrame == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final CoordI2 viewPortDimension = this.rendererData.getRendererControl().getAbsoluteViewportDimension();
|
||||||
|
|
||||||
|
if (viewPortDimension.getX() < 10 || viewPortDimension.getY() < 10) {
|
||||||
|
// Some race condition happens rarely (mainly Windows 11) that window size is not initialized after entering full screen. Put a plaster on it...
|
||||||
|
throw new G2DRendererWindowNotInitializedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
BufferedImage screenshotImg = null;
|
||||||
|
Graphics2D screenshotGraphics = null;
|
||||||
|
final boolean takeScreenshot_ = this.takeScreenshot;
|
||||||
|
if (takeScreenshot_) {
|
||||||
|
this.takeScreenshot = false;
|
||||||
|
screenshotImg = this.mainFrame.getGraphicsConfiguration().createCompatibleImage(viewPortDimension.getX(), viewPortDimension.getY());
|
||||||
|
screenshotGraphics = screenshotImg.createGraphics();
|
||||||
|
screenshotGraphics.setRenderingHints(G2DRenderingHintFactory.createDefault());
|
||||||
|
}
|
||||||
|
|
||||||
|
this.addStaticRenderingBOs(renderingBOs);
|
||||||
|
renderingBOs.sort((bo0, bo1) -> bo0.getLayer().compareTo(bo1.getLayer()));
|
||||||
|
|
||||||
|
Graphics2D screenGraphics = null;
|
||||||
|
try {
|
||||||
|
final BufferStrategy bufferStrategy = this.mainFrame.getBufferStrategy();
|
||||||
|
if (bufferStrategy == null) {
|
||||||
|
LOGGER.warn("bufferStrategy == null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
screenGraphics = (Graphics2D) bufferStrategy.getDrawGraphics();
|
||||||
|
screenGraphics.setRenderingHints(G2DRenderingHintFactory.createDefault());
|
||||||
|
screenGraphics.setColor(Color.BLACK);
|
||||||
|
screenGraphics.fillRect(0, 0, viewPortDimension.getX(), viewPortDimension.getY());
|
||||||
|
|
||||||
|
for (final RenderingBo renderingBO : renderingBOs) {
|
||||||
|
if (renderingBO instanceof G2DAbstractRenderingBo) {
|
||||||
|
((G2DAbstractRenderingBo) renderingBO).render(screenGraphics, this.rendererData.getRendererControl().getScaleFactor(), false);
|
||||||
|
if (takeScreenshot_ && ((G2DAbstractRenderingBo) renderingBO).isScreenshotRelevant()) {
|
||||||
|
((G2DAbstractRenderingBo) renderingBO).render(screenshotGraphics, this.rendererData.getRendererControl().getScaleFactor(), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Rendering cycle END
|
||||||
|
//
|
||||||
|
|
||||||
|
this.frameCap();
|
||||||
|
|
||||||
|
//
|
||||||
|
// Rendering cycle START
|
||||||
|
//
|
||||||
|
|
||||||
|
bufferStrategy.show();
|
||||||
|
Toolkit.getDefaultToolkit().sync();
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
if (screenGraphics != null) {
|
||||||
|
screenGraphics.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final RenderingBo renderingBO : renderingBOs) {
|
||||||
|
this.rendererData.getRenderingBoPool().release(renderingBO);
|
||||||
|
}
|
||||||
|
// TODO discover with marker interface
|
||||||
|
G2DDrawControl.closeStatic();
|
||||||
|
G2DDrawImage.closeStatic();
|
||||||
|
G2DDrawControlTooltip.closeStatic();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (takeScreenshot_) {
|
||||||
|
this.publishScreenshot(screenshotImg, screenshotGraphics);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
this.semaphore.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (final G2DRendererWindowNotInitializedException e) {
|
||||||
|
LOGGER.error("G2DRendererWindowNotInitializedException");
|
||||||
|
Thread.sleep(250l);
|
||||||
|
this.rendererData.getRendererControl().applyVideoOptions();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addStaticRenderingBOs(final List<RenderingBo> renderingBOs) {
|
||||||
|
renderingBOs.add(this.rendererData.getRenderingBoPool().acquire(G2DDrawBackground.class));
|
||||||
|
renderingBOs.add(this.rendererData.getRenderingBoPool().acquire(G2DDrawControlTooltip.class));
|
||||||
|
this.drawStatistics(renderingBOs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawStatistics(final List<RenderingBo> renderingBOs) {
|
||||||
|
if (!this.rendererData.getUiOptions().isDrawFps()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FPS
|
||||||
|
|
||||||
|
final StringBuilder sb = new StringBuilder(15);
|
||||||
|
sb.append(this.fps);
|
||||||
|
sb.append("fps ");
|
||||||
|
sb.append(this.frameDur);
|
||||||
|
sb.append("ms");
|
||||||
|
|
||||||
|
final RenderingBo drawFont = this.rendererData.getRenderingBoPool().acquire(DrawFontRenderingBo.class)
|
||||||
|
.setText(sb.toString())
|
||||||
|
.setSize(14)
|
||||||
|
.setColor(RgbaColor.WHITE)
|
||||||
|
.setLayer(RenderingBoLayer.TOP)
|
||||||
|
.withPositionAbsoluteAnchorTopRight(100, 35);
|
||||||
|
renderingBOs.add(drawFont);
|
||||||
|
|
||||||
|
this.fpsHistory.add(Integer.valueOf((int) this.frameDur));
|
||||||
|
final RenderingBo drawFpsHistory = this.rendererData.getRenderingBoPool().acquire(G2DDrawFpsHistory.class)
|
||||||
|
.setHistory(this.fpsHistory)
|
||||||
|
.setLayer(RenderingBoLayer.TOP)
|
||||||
|
.withPositionAbsoluteAnchorTopRight(100, 50);
|
||||||
|
renderingBOs.add(drawFpsHistory);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void frameCap() throws InterruptedException {
|
||||||
|
final long fpsCurrentTime = System.currentTimeMillis();
|
||||||
|
if (fpsCurrentTime - this.fpsMeasurement >= 1000) {
|
||||||
|
this.fpsMeasurement = fpsCurrentTime;
|
||||||
|
this.fps = this.fpsCur;
|
||||||
|
this.fpsCur = 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.fpsCur++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.frameStart == 0) {
|
||||||
|
this.frameStart = System.nanoTime();
|
||||||
|
}
|
||||||
|
final long target = this.frameStart + 1000000000L / this.rendererData.getRendererControl().getFrameRate();
|
||||||
|
this.frameDur = (System.nanoTime() - this.frameStart) / 1000000L;
|
||||||
|
final long delta = target - System.nanoTime();
|
||||||
|
spinWait(delta);
|
||||||
|
this.frameStart = System.nanoTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void spinWait(final long nanoDuration) throws InterruptedException {
|
||||||
|
final long end = System.nanoTime() + nanoDuration;
|
||||||
|
long timeLeft = nanoDuration;
|
||||||
|
do {
|
||||||
|
if (timeLeft > SLEEP_PRECISION) {
|
||||||
|
Thread.sleep(1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Thread.yield();
|
||||||
|
}
|
||||||
|
|
||||||
|
timeLeft = end - System.nanoTime();
|
||||||
|
} while (timeLeft > 0);
|
||||||
|
|
||||||
|
if (Thread.interrupted()) {
|
||||||
|
throw new InterruptedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void publishScreenshot(final BufferedImage screenshotImg, final Graphics2D screenshotGraphics) {
|
||||||
|
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
|
||||||
|
ImageIO.write(screenshotImg, "png", baos);
|
||||||
|
baos.flush();
|
||||||
|
|
||||||
|
LOGGER.debug("Took screenshot");
|
||||||
|
|
||||||
|
this.rendererData.getEventConsumer().accept(new TakeScreenshotEvent(null, baos.toByteArray()));
|
||||||
|
}
|
||||||
|
catch (final IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
screenshotGraphics.dispose();
|
||||||
|
screenshotImg.flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public G2DMainFrame getMainFrame() {
|
||||||
|
return this.mainFrame;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMainFrame(final G2DMainFrame mainFrame) {
|
||||||
|
this.mainFrame = mainFrame;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Semaphore getSemaphore() {
|
||||||
|
return this.semaphore;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTitle(final String title) {
|
||||||
|
this.title = title;
|
||||||
|
if (this.mainFrame != null) {
|
||||||
|
this.mainFrame.setTitle(title);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTitle() {
|
||||||
|
return this.title;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,249 @@
|
|||||||
|
package de.extio.game_engine.renderer.g2d;
|
||||||
|
|
||||||
|
import java.awt.EventQueue;
|
||||||
|
import java.awt.Toolkit;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import de.extio.game_engine.renderer.RendererControl;
|
||||||
|
import de.extio.game_engine.renderer.RendererData;
|
||||||
|
import de.extio.game_engine.renderer.RendererData.VideoOptions.VideoOptionsVideoMode;
|
||||||
|
import de.extio.game_engine.spatial2.model.CoordI2;
|
||||||
|
import de.extio.game_engine.spatial2.model.ImmutableCoordI2;
|
||||||
|
|
||||||
|
public class G2DRendererControl implements RendererControl {
|
||||||
|
|
||||||
|
protected static final Logger LOGGER = LoggerFactory.getLogger(G2DRendererControl.class);
|
||||||
|
|
||||||
|
private final static CoordI2 NO_SCALING_UPPER_LIMIT = ImmutableCoordI2.create(2560, 1440);
|
||||||
|
|
||||||
|
private final RendererData rendererData;
|
||||||
|
|
||||||
|
private final G2DRenderer renderer;
|
||||||
|
|
||||||
|
private CoordI2 absoluteViewportDimension = REFERENCE_RESOLUTION;
|
||||||
|
|
||||||
|
private CoordI2 effectiveViewportDimension = REFERENCE_RESOLUTION;
|
||||||
|
|
||||||
|
private double scaleFactor = 1.0;
|
||||||
|
|
||||||
|
private double scaleFactorModifier = 1.0;
|
||||||
|
|
||||||
|
private boolean forceAutoScaling;
|
||||||
|
|
||||||
|
private int frameRate = 60;
|
||||||
|
|
||||||
|
private volatile long videoOptionsAppliedAt;
|
||||||
|
|
||||||
|
public G2DRendererControl(final G2DRenderer renderer, final RendererData rendererData) {
|
||||||
|
this.renderer = renderer;
|
||||||
|
this.rendererData = rendererData;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTitle(final String title) {
|
||||||
|
this.renderer.setTitle(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void applyVideoOptions() {
|
||||||
|
final Runnable run = () -> {
|
||||||
|
try {
|
||||||
|
this.renderer.getSemaphore().acquire();
|
||||||
|
}
|
||||||
|
catch (final InterruptedException e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (this.renderer.getMainFrame() != null) {
|
||||||
|
this.renderer.getMainFrame().unregisterFullScreenWindow();
|
||||||
|
this.renderer.reset();
|
||||||
|
this.renderer.getMainFrame().setVisible(false);
|
||||||
|
this.renderer.getMainFrame().dispose();
|
||||||
|
this.renderer.setMainFrame(null);
|
||||||
|
Toolkit.getDefaultToolkit().sync();
|
||||||
|
try {
|
||||||
|
Thread.sleep(100l);
|
||||||
|
}
|
||||||
|
catch (final InterruptedException e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final G2DMainFrame mainFrame = new G2DMainFrame(this.rendererData);
|
||||||
|
if (this.rendererData.getVideoOptions().getVideoMode() == VideoOptionsVideoMode.FULLSCREEN) {
|
||||||
|
mainFrame.fullscreen();
|
||||||
|
}
|
||||||
|
else if (this.rendererData.getVideoOptions().getVideoMode() == VideoOptionsVideoMode.BORDERLESS) {
|
||||||
|
mainFrame.borderless();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
mainFrame.windowed();
|
||||||
|
}
|
||||||
|
mainFrame.setTitle(this.renderer.getTitle());
|
||||||
|
if (this.rendererData.getVideoOptions().getFrameRateTarget() == 0) {
|
||||||
|
this.rendererData.getVideoOptions().setFrameRateTarget(this.frameRate);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.frameRate = this.rendererData.getVideoOptions().getFrameRateTarget();
|
||||||
|
}
|
||||||
|
this.renderer.setMainFrame(mainFrame);
|
||||||
|
|
||||||
|
Toolkit.getDefaultToolkit().sync();
|
||||||
|
|
||||||
|
this.updateViewPort(false, false);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
this.renderer.getSemaphore().release();
|
||||||
|
this.videoOptionsAppliedAt = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (EventQueue.isDispatchThread()) {
|
||||||
|
run.run();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
try {
|
||||||
|
EventQueue.invokeAndWait(run);
|
||||||
|
}
|
||||||
|
catch (InvocationTargetException | InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getVideoOptionsAppliedAt() {
|
||||||
|
return this.videoOptionsAppliedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CoordI2 getEffectiveViewportDimension() {
|
||||||
|
return this.effectiveViewportDimension;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CoordI2 getAbsoluteViewportDimension() {
|
||||||
|
return this.absoluteViewportDimension;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getScaleFactor() {
|
||||||
|
return this.scaleFactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getScaleFactorModifier() {
|
||||||
|
return this.scaleFactorModifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setScaleFactorModifier(final double scaleFactorModifier) {
|
||||||
|
this.scaleFactorModifier = scaleFactorModifier;
|
||||||
|
this.recalculate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isForceAutoScaling() {
|
||||||
|
return this.forceAutoScaling;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setForceAutoScaling(final boolean forceAutoScaling) {
|
||||||
|
this.forceAutoScaling = forceAutoScaling;
|
||||||
|
this.recalculate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateViewPort(final boolean lockRenderer, final boolean async) {
|
||||||
|
// This method is called several times in a row by mainFrame resize event handler by intention
|
||||||
|
// I've discovered in debugger that sometimes when only a black viewPort is displayed the volatile image backing the bufferStrategy is only a few pixels in size
|
||||||
|
// Letting the resize events do their job seemed to fix it
|
||||||
|
|
||||||
|
final Runnable run = () -> {
|
||||||
|
if (lockRenderer) {
|
||||||
|
try {
|
||||||
|
this.renderer.getSemaphore().acquire();
|
||||||
|
}
|
||||||
|
catch (final InterruptedException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
this.updateAbsoluteViewportPortDimension();
|
||||||
|
this.recalculate();
|
||||||
|
this.renderer.getMainFrame().createBufferStrategy();
|
||||||
|
this.renderer.getMainFrame().resizeListenerEnabled = true;
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
if (lockRenderer) {
|
||||||
|
this.renderer.getSemaphore().release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (async) {
|
||||||
|
EventQueue.invokeLater(run);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
run.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void recalculate() {
|
||||||
|
this.calculateScaleFactor();
|
||||||
|
this.calculateViewportEffectiveDimension();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateAbsoluteViewportPortDimension() {
|
||||||
|
final CoordI2 absoluteDim = this.absoluteViewportDimension;
|
||||||
|
if (absoluteDim == null || absoluteDim.getX() != this.renderer.getMainFrame().getWidth() || absoluteDim.getY() != this.renderer.getMainFrame().getHeight()) {
|
||||||
|
this.absoluteViewportDimension = ImmutableCoordI2.create(this.renderer.getMainFrame().getWidth(), this.renderer.getMainFrame().getHeight());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void calculateViewportEffectiveDimension() {
|
||||||
|
this.effectiveViewportDimension = ImmutableCoordI2.create((int) (this.absoluteViewportDimension.getX() / this.scaleFactor), (int) (this.absoluteViewportDimension.getY() / this.scaleFactor));
|
||||||
|
if (LOGGER.isDebugEnabled()) LOGGER.debug("Viewport absolute dimension: {}", this.absoluteViewportDimension);
|
||||||
|
if (LOGGER.isDebugEnabled()) LOGGER.debug("Viewport effective dimension: {}", this.effectiveViewportDimension);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void calculateScaleFactor() {
|
||||||
|
final double fLow = Math.min(this.absoluteViewportDimension.getX() / (double) REFERENCE_RESOLUTION.getX(), this.absoluteViewportDimension.getY() / (double) REFERENCE_RESOLUTION.getY());
|
||||||
|
final double fHigh = Math.min(this.absoluteViewportDimension.getX() / (double) NO_SCALING_UPPER_LIMIT.getX(), this.absoluteViewportDimension.getY() / (double) NO_SCALING_UPPER_LIMIT.getY());
|
||||||
|
|
||||||
|
double factor;
|
||||||
|
if (this.isForceAutoScaling()) {
|
||||||
|
factor = fLow;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (fLow < 1.0 && fHigh < 1.0) {
|
||||||
|
factor = fLow * this.scaleFactorModifier;
|
||||||
|
}
|
||||||
|
else if (fLow > 1.0 && fHigh > 1.0) {
|
||||||
|
factor = fHigh * this.scaleFactorModifier;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
factor = this.scaleFactorModifier;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.scaleFactor = Math.min(SCALE_FACTOR_MAX, Math.max(SCALE_FACTOR_MIN, factor));
|
||||||
|
|
||||||
|
if (LOGGER.isDebugEnabled()) LOGGER.debug("Viewport scale factor: {}", this.scaleFactor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getFrameRate() {
|
||||||
|
return this.frameRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFrameRate(final int frameRate) {
|
||||||
|
this.frameRate = frameRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getFrame() {
|
||||||
|
return this.rendererData.getFrame();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package de.extio.game_engine.renderer.g2d;
|
||||||
|
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
class G2DRendererWindowNotInitializedException extends Exception {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package de.extio.game_engine.renderer.g2d;
|
||||||
|
|
||||||
|
import java.awt.RenderingHints;
|
||||||
|
import java.awt.RenderingHints.Key;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class G2DRenderingHintFactory {
|
||||||
|
|
||||||
|
private final static RenderingHints renderingDefault;
|
||||||
|
|
||||||
|
private final static RenderingHints renderingHintsTiles;
|
||||||
|
|
||||||
|
private final static RenderingHints renderingHintsTilesBlocky;
|
||||||
|
|
||||||
|
static {
|
||||||
|
Map<Key, Object> hints = new HashMap<>();
|
||||||
|
hints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
|
||||||
|
hints.put(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
|
||||||
|
hints.put(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
|
||||||
|
hints.put(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
|
||||||
|
hints.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||||
|
hints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
|
||||||
|
hints.put(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE);
|
||||||
|
renderingDefault = new RenderingHints(hints);
|
||||||
|
|
||||||
|
hints = new HashMap<>();
|
||||||
|
hints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
|
hints.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
|
||||||
|
hints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
|
||||||
|
renderingHintsTiles = new RenderingHints(hints);
|
||||||
|
|
||||||
|
hints = new HashMap<>();
|
||||||
|
hints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
|
hints.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
|
||||||
|
hints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
|
||||||
|
renderingHintsTilesBlocky = new RenderingHints(hints);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RenderingHints createDefault() {
|
||||||
|
return renderingDefault;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RenderingHints createForTiles() {
|
||||||
|
return renderingHintsTiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RenderingHints createForTilesBlocky() {
|
||||||
|
return renderingHintsTilesBlocky;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package de.extio.game_engine.renderer.g2d.bo.rendering;
|
||||||
|
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
|
||||||
|
import de.extio.game_engine.renderer.model.AbstractRenderingBo;
|
||||||
|
import de.extio.game_engine.renderer.model.RenderingBoLayer;
|
||||||
|
|
||||||
|
public abstract class G2DAbstractRenderingBo extends AbstractRenderingBo {
|
||||||
|
|
||||||
|
public G2DAbstractRenderingBo(final RenderingBoLayer layer) {
|
||||||
|
super(layer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isScreenshotRelevant() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void render(Graphics2D graphics, double scaleFactor, boolean force);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,242 @@
|
|||||||
|
package de.extio.game_engine.renderer.g2d.bo.rendering;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Graphics;
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.GraphicsConfiguration;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.awt.image.VolatileImage;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
|
||||||
|
import de.extio.game_engine.renderer.g2d.G2DRendererControl;
|
||||||
|
import de.extio.game_engine.renderer.model.ImmutableRgbaColor;
|
||||||
|
import de.extio.game_engine.renderer.model.RenderingBoLayer;
|
||||||
|
import de.extio.game_engine.renderer.model.RgbaColor;
|
||||||
|
import de.extio.game_engine.spatial2.model.CoordI2;
|
||||||
|
import de.extio.game_engine.spatial2.model.ImmutableCoordI2;
|
||||||
|
import de.extio.game_engine.spatial2.model.MutableCoordI2;
|
||||||
|
import de.extio.game_engine.util.rng.ThreadLocalXorShift128Random;
|
||||||
|
|
||||||
|
public class G2DDrawBackground extends G2DAbstractRenderingBo {
|
||||||
|
|
||||||
|
private static VolatileImage BACKGROUND_IMAGE;
|
||||||
|
|
||||||
|
private static BufferedImage BACKGROUND_BUFFEREDIMAGE;
|
||||||
|
|
||||||
|
private static String BACKGROUND_KEY;
|
||||||
|
|
||||||
|
private final static int[] BUILTIN_SCROLL_OFFSET_X = new int[2];
|
||||||
|
|
||||||
|
private final CoordI2 bgrOffset = MutableCoordI2.create();
|
||||||
|
|
||||||
|
private final CoordI2 sourceOffset = MutableCoordI2.create();
|
||||||
|
|
||||||
|
private final CoordI2 destPosition = MutableCoordI2.create();
|
||||||
|
|
||||||
|
private final CoordI2 destPosition2 = MutableCoordI2.create();
|
||||||
|
|
||||||
|
private final CoordI2 overflow = MutableCoordI2.create();
|
||||||
|
|
||||||
|
private final List<Star> stars = new ArrayList<>();
|
||||||
|
|
||||||
|
private CoordI2 starsViewport = ImmutableCoordI2.create();
|
||||||
|
|
||||||
|
public G2DDrawBackground() {
|
||||||
|
super(RenderingBoLayer.BACKGROUND0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isScreenshotRelevant() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render(final Graphics2D graphics, final double scaleFactor, final boolean force) {
|
||||||
|
final CoordI2 windowDim = ((G2DRendererControl) this.rendererData.getRendererControl()).getAbsoluteViewportDimension();
|
||||||
|
|
||||||
|
this.scroll(0, this.rendererData.getUiOptions().isBackgroundScrolling0(), this.rendererData.getUiOptions().isBackgroundScrollingReverse0(), windowDim);
|
||||||
|
this.tileBackground(graphics, 0, this.rendererData.getUiOptions().getBackgroundResourceName0(),
|
||||||
|
this.bgrOffset.setXY(this.rendererData.getUiOptions().getBackgroundOffset0()).add(BUILTIN_SCROLL_OFFSET_X[0], 0), windowDim);
|
||||||
|
|
||||||
|
this.scroll(1, this.rendererData.getUiOptions().isBackgroundScrolling1(), this.rendererData.getUiOptions().isBackgroundScrollingReverse1(), windowDim);
|
||||||
|
this.drawStars(graphics, this.bgrOffset.setXY(this.rendererData.getUiOptions().getBackgroundOffset1()).add(BUILTIN_SCROLL_OFFSET_X[1], 0), windowDim);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tileBackground(final Graphics2D graphics, final int index, final String backgroundName, final CoordI2 offset, final CoordI2 viewPort) {
|
||||||
|
if (!Objects.equals(backgroundName, G2DDrawBackground.BACKGROUND_KEY)) {
|
||||||
|
this.loadImage(graphics, index, backgroundName);
|
||||||
|
}
|
||||||
|
if (G2DDrawBackground.BACKGROUND_IMAGE == null) {
|
||||||
|
if (index == 0) {
|
||||||
|
this.fillBlack(graphics, viewPort);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (BACKGROUND_IMAGE.validate(graphics.getDeviceConfiguration()) != VolatileImage.IMAGE_OK) {
|
||||||
|
this.createVolatileBackgroundImage(graphics, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sourceOffset.setXY(Math.floorMod(offset.getX(), G2DDrawBackground.BACKGROUND_IMAGE.getWidth()), Math.floorMod(offset.getY(), G2DDrawBackground.BACKGROUND_IMAGE.getHeight()));
|
||||||
|
this.destPosition.setXY(0, 0);
|
||||||
|
do {
|
||||||
|
this.destPosition2
|
||||||
|
.setXY(this.destPosition)
|
||||||
|
.add(G2DDrawBackground.BACKGROUND_IMAGE.getWidth(), G2DDrawBackground.BACKGROUND_IMAGE.getHeight())
|
||||||
|
.substract(this.sourceOffset);
|
||||||
|
|
||||||
|
this.overflow
|
||||||
|
.setXY(viewPort)
|
||||||
|
.substract(this.destPosition2);
|
||||||
|
|
||||||
|
graphics.drawImage(G2DDrawBackground.BACKGROUND_IMAGE,
|
||||||
|
this.destPosition.getX(), //dx1
|
||||||
|
this.destPosition.getY(),
|
||||||
|
this.destPosition2.getX() + Math.min(0, this.overflow.getX()), //dx2
|
||||||
|
this.destPosition2.getY() + Math.min(0, this.overflow.getY()),
|
||||||
|
this.sourceOffset.getX(), //sx1
|
||||||
|
this.sourceOffset.getY(),
|
||||||
|
G2DDrawBackground.BACKGROUND_IMAGE.getWidth() + Math.min(0, this.overflow.getX()), //sx2
|
||||||
|
G2DDrawBackground.BACKGROUND_IMAGE.getHeight() + Math.min(0, this.overflow.getY()),
|
||||||
|
null);
|
||||||
|
|
||||||
|
this.destPosition.add(G2DDrawBackground.BACKGROUND_IMAGE.getWidth() - this.sourceOffset.getX(), 0);
|
||||||
|
if (this.destPosition.getX() < viewPort.getX()) {
|
||||||
|
this.sourceOffset.setX(0);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.destPosition.add(0, G2DDrawBackground.BACKGROUND_IMAGE.getHeight() - this.sourceOffset.getY());
|
||||||
|
this.destPosition.setX(0);
|
||||||
|
this.sourceOffset.setXY(Math.floorMod(offset.getX(), G2DDrawBackground.BACKGROUND_IMAGE.getWidth()), 0);
|
||||||
|
}
|
||||||
|
} while (this.destPosition.getY() < viewPort.getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawStars(final Graphics2D graphics, final CoordI2 offset, final CoordI2 viewPort) {
|
||||||
|
this.generateStars(viewPort);
|
||||||
|
|
||||||
|
for (final Star element : this.stars) {
|
||||||
|
graphics.setColor(element.color.toAwtColor());
|
||||||
|
graphics.fillOval(
|
||||||
|
Math.floorMod(element.position.getX() - offset.getX(), viewPort.getX()),
|
||||||
|
Math.floorMod(element.position.getY() - offset.getY(), viewPort.getY()),
|
||||||
|
element.radius.getX(),
|
||||||
|
element.radius.getY());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scroll(final int index, final boolean scrolling, final boolean reverse, final CoordI2 viewPort) {
|
||||||
|
if (!scrolling) {
|
||||||
|
BUILTIN_SCROLL_OFFSET_X[index] = 0;
|
||||||
|
}
|
||||||
|
else if (this.rendererData.getFrame() % Math.max(1, this.rendererData.getRendererControl().getFrameRate() / 30) == 0) {
|
||||||
|
if (G2DDrawBackground.BACKGROUND_IMAGE != null) {
|
||||||
|
this.bgrOffset.setXY(G2DDrawBackground.BACKGROUND_IMAGE.getWidth(), G2DDrawBackground.BACKGROUND_IMAGE.getHeight());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.bgrOffset.setXY(viewPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reverse) {
|
||||||
|
if (--BUILTIN_SCROLL_OFFSET_X[index] < -this.bgrOffset.getX()) {
|
||||||
|
BUILTIN_SCROLL_OFFSET_X[index] += this.bgrOffset.getX();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (++BUILTIN_SCROLL_OFFSET_X[index] > this.bgrOffset.getX()) {
|
||||||
|
BUILTIN_SCROLL_OFFSET_X[index] -= this.bgrOffset.getX();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fillBlack(final Graphics2D graphics, final CoordI2 viewPort) {
|
||||||
|
graphics.setColor(Color.BLACK);
|
||||||
|
graphics.fillRect(0, 0, viewPort.getX(), viewPort.getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadImage(final Graphics2D graphics, final int index, final String backgroundName) {
|
||||||
|
if (G2DDrawBackground.BACKGROUND_IMAGE != null) {
|
||||||
|
G2DDrawBackground.BACKGROUND_IMAGE.flush();
|
||||||
|
G2DDrawBackground.BACKGROUND_BUFFEREDIMAGE.flush();
|
||||||
|
}
|
||||||
|
G2DDrawBackground.BACKGROUND_IMAGE = null;
|
||||||
|
G2DDrawBackground.BACKGROUND_BUFFEREDIMAGE = null;
|
||||||
|
G2DDrawBackground.BACKGROUND_KEY = null;
|
||||||
|
G2DDrawBackground.BUILTIN_SCROLL_OFFSET_X[index] = 0;
|
||||||
|
|
||||||
|
if (backgroundName != null && !backgroundName.isEmpty()) {
|
||||||
|
try (InputStream stream = new FileInputStream(backgroundName)) {
|
||||||
|
if (stream != null) {
|
||||||
|
G2DDrawBackground.BACKGROUND_KEY = backgroundName;
|
||||||
|
|
||||||
|
G2DDrawBackground.BACKGROUND_BUFFEREDIMAGE = ImageIO.read(stream);
|
||||||
|
|
||||||
|
this.createVolatileBackgroundImage(graphics, index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (final IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createVolatileBackgroundImage(final Graphics2D graphics, final int index) {
|
||||||
|
if (G2DDrawBackground.BACKGROUND_IMAGE != null) {
|
||||||
|
G2DDrawBackground.BACKGROUND_IMAGE.flush();
|
||||||
|
}
|
||||||
|
G2DDrawBackground.BACKGROUND_IMAGE = null;
|
||||||
|
|
||||||
|
final GraphicsConfiguration gc = graphics.getDeviceConfiguration();
|
||||||
|
G2DDrawBackground.BACKGROUND_IMAGE = gc.createCompatibleVolatileImage(G2DDrawBackground.BACKGROUND_BUFFEREDIMAGE.getWidth(), G2DDrawBackground.BACKGROUND_BUFFEREDIMAGE.getHeight());
|
||||||
|
G2DDrawBackground.BACKGROUND_IMAGE.validate(gc);
|
||||||
|
Graphics g = null;
|
||||||
|
try {
|
||||||
|
g = G2DDrawBackground.BACKGROUND_IMAGE.createGraphics();
|
||||||
|
g.drawImage(G2DDrawBackground.BACKGROUND_BUFFEREDIMAGE, 0, 0, null);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
if (g != null) {
|
||||||
|
g.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void generateStars(final CoordI2 viewPort) {
|
||||||
|
if (!viewPort.equals(this.starsViewport)) {
|
||||||
|
this.starsViewport = viewPort.toImmutableCoordI2();
|
||||||
|
final Random rand = ThreadLocalXorShift128Random.current();
|
||||||
|
this.stars.clear();
|
||||||
|
for (int i = 0; i < 350; i++) {
|
||||||
|
this.stars.add(new Star(
|
||||||
|
ImmutableCoordI2.create(rand.nextInt(this.starsViewport.getX()), rand.nextInt(this.starsViewport.getY())),
|
||||||
|
ImmutableCoordI2.create(rand.nextInt(3) + 2, rand.nextInt(3) + 2),
|
||||||
|
new ImmutableRgbaColor(rand.nextInt(30) + 100, rand.nextInt(30) + 100, rand.nextInt(30) + 130)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Star {
|
||||||
|
|
||||||
|
CoordI2 position;
|
||||||
|
|
||||||
|
CoordI2 radius;
|
||||||
|
|
||||||
|
RgbaColor color;
|
||||||
|
|
||||||
|
Star(final CoordI2 position, final CoordI2 radius, final RgbaColor color) {
|
||||||
|
super();
|
||||||
|
this.position = position;
|
||||||
|
this.radius = radius;
|
||||||
|
this.color = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,295 @@
|
|||||||
|
package de.extio.game_engine.renderer.g2d.bo.rendering;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Frame;
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
import de.extio.game_engine.renderer.g2d.G2DRenderer;
|
||||||
|
import de.extio.game_engine.renderer.g2d.control.components.ComponentRenderingSupport;
|
||||||
|
import de.extio.game_engine.renderer.model.bo.DrawEffectRenderingBo;
|
||||||
|
import de.extio.game_engine.renderer.model.DrawEffectRenderingBoEffects;
|
||||||
|
import de.extio.game_engine.renderer.model.RenderingBo;
|
||||||
|
import de.extio.game_engine.renderer.model.RenderingBoLayer;
|
||||||
|
import de.extio.game_engine.renderer.model.RgbaColor;
|
||||||
|
import de.extio.game_engine.spatial2.WorldUtils2;
|
||||||
|
import de.extio.game_engine.spatial2.model.Area2;
|
||||||
|
import de.extio.game_engine.spatial2.model.CoordD2;
|
||||||
|
import de.extio.game_engine.spatial2.model.CoordI2;
|
||||||
|
import de.extio.game_engine.spatial2.model.ImmutableCoordD2;
|
||||||
|
import de.extio.game_engine.spatial2.model.ImmutableCoordI2;
|
||||||
|
import de.extio.game_engine.spatial2.model.MutableCoordD2;
|
||||||
|
import de.extio.game_engine.spatial2.model.MutableCoordI2;
|
||||||
|
|
||||||
|
public class G2DDrawEffect extends G2DAbstractRenderingBo implements DrawEffectRenderingBo {
|
||||||
|
|
||||||
|
private DrawEffectRenderingBoEffects effect;
|
||||||
|
|
||||||
|
private List<CoordI2> relativeCoordinates;
|
||||||
|
|
||||||
|
private int customInt0;
|
||||||
|
|
||||||
|
private double customDouble0;
|
||||||
|
|
||||||
|
private String customString0;
|
||||||
|
|
||||||
|
public G2DDrawEffect() {
|
||||||
|
super(RenderingBoLayer.UI0_2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render(final Graphics2D graphics, final double scaleFactor, final boolean force) {
|
||||||
|
if (this.effect == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int x0 = (int) (this.x * scaleFactor);
|
||||||
|
int y0 = (int) (this.y * scaleFactor);
|
||||||
|
|
||||||
|
List<CoordI2> coordinates = null;
|
||||||
|
if (this.relativeCoordinates != null && !this.relativeCoordinates.isEmpty()) {
|
||||||
|
coordinates = new ArrayList<>(this.relativeCoordinates.size());
|
||||||
|
for (final CoordI2 relativeCoord : this.relativeCoordinates) {
|
||||||
|
coordinates.add(ImmutableCoordI2.create((int) (relativeCoord.getX() * scaleFactor), (int) (relativeCoord.getY() * scaleFactor)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (this.effect) {
|
||||||
|
case LINE: {
|
||||||
|
if (coordinates == null || coordinates.isEmpty()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
final int x1 = x0 + coordinates.get(0).getX();
|
||||||
|
final int y1 = y0 + coordinates.get(0).getY();
|
||||||
|
|
||||||
|
final Area2 area = WorldUtils2.pointsToArea(ImmutableCoordI2.create(x0, y0), ImmutableCoordI2.create(x1, y1));
|
||||||
|
if (this.cull(area)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
graphics.setColor(this.color == null ? Color.WHITE : this.color.toAwtColor());
|
||||||
|
graphics.drawLine(x0, y0, x1, y1);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case RECT:
|
||||||
|
case RECT_FILLED: {
|
||||||
|
if (coordinates == null || coordinates.isEmpty()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
final int x1 = x0 + coordinates.get(0).getX();
|
||||||
|
final int y1 = y0 + coordinates.get(0).getY();
|
||||||
|
|
||||||
|
final Area2 area = WorldUtils2.pointsToArea(ImmutableCoordI2.create(x0, y0), ImmutableCoordI2.create(x1, y1));
|
||||||
|
if (this.cull(area)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
graphics.setColor(this.color == null ? Color.WHITE : this.color.toAwtColor());
|
||||||
|
if (this.effect == DrawEffectRenderingBoEffects.RECT) {
|
||||||
|
graphics.drawRect(area.getPosition().getX(), area.getPosition().getY(), area.getDimension().getX(), area.getDimension().getY());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
graphics.fillRect(area.getPosition().getX(), area.getPosition().getY(), area.getDimension().getX(), area.getDimension().getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case CIRCLE:
|
||||||
|
case CIRCLE_FILLED: {
|
||||||
|
final int diameter = Math.max(3, (int) Math.round(this.customInt0 * scaleFactor));
|
||||||
|
|
||||||
|
final Area2 area = new Area2(ImmutableCoordI2.create(x0, y0), ImmutableCoordI2.create(diameter, diameter));
|
||||||
|
if (this.cull(area)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
graphics.setColor(this.color == null ? Color.WHITE : this.color.toAwtColor());
|
||||||
|
if (this.effect == DrawEffectRenderingBoEffects.CIRCLE) {
|
||||||
|
graphics.drawOval(x0, y0, diameter, diameter);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
graphics.fillOval(x0, y0, diameter, diameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case CIRCLE_CENTERED:
|
||||||
|
case CIRCLE_CENTERED_FILLED: {
|
||||||
|
final int radius = Math.max(3, (int) Math.round(this.customInt0 * scaleFactor));
|
||||||
|
|
||||||
|
final Area2 area = new Area2(ImmutableCoordI2.create(x0, y0).substract(radius), ImmutableCoordI2.zero().add(radius * 2));
|
||||||
|
if (this.cull(area)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
graphics.setColor(this.color == null ? Color.WHITE : this.color.toAwtColor());
|
||||||
|
if (this.effect == DrawEffectRenderingBoEffects.CIRCLE_CENTERED) {
|
||||||
|
graphics.drawOval(x0 - radius, y0 - radius, radius * 2, radius * 2);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
graphics.fillOval(x0 - radius, y0 - radius, radius * 2, radius * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case ARROW: {
|
||||||
|
if (coordinates == null || coordinates.isEmpty()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
final int x1 = x0 + coordinates.get(0).getX();
|
||||||
|
final int y1 = y0 + coordinates.get(0).getY();
|
||||||
|
|
||||||
|
final double width = Math.max(1, Math.max(Math.abs(x0 - x1), Math.abs(y0 - y1)) / 5);
|
||||||
|
final double height = width;
|
||||||
|
final int dx = x1 - x0;
|
||||||
|
final int dy = y1 - y0;
|
||||||
|
final double D = Math.max(1.0, Math.sqrt(Math.pow(dx, 2.0) + Math.pow(dy, 2.0)));
|
||||||
|
double xm = D - width;
|
||||||
|
double xn = xm;
|
||||||
|
double ym = height;
|
||||||
|
double yn = -height;
|
||||||
|
double x;
|
||||||
|
final double sin = dy / D;
|
||||||
|
final double cos = dx / D;
|
||||||
|
|
||||||
|
x = xm * cos - ym * sin + x0;
|
||||||
|
ym = xm * sin + ym * cos + y0;
|
||||||
|
xm = x;
|
||||||
|
|
||||||
|
x = xn * cos - yn * sin + x0;
|
||||||
|
yn = xn * sin + yn * cos + y0;
|
||||||
|
xn = x;
|
||||||
|
|
||||||
|
final Area2 area = WorldUtils2.pointsToArea(ImmutableCoordI2.create(x0, y0), ImmutableCoordI2.create(x1, y1), ImmutableCoordI2.create((int) xm, (int) ym), ImmutableCoordI2.create((int) xn, (int) yn));
|
||||||
|
if (this.cull(area)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
graphics.setColor(this.color == null ? Color.WHITE : this.color.toAwtColor());
|
||||||
|
graphics.drawLine(x0, y0, x1, y1);
|
||||||
|
graphics.drawLine(x1, y1, (int) xm, (int) ym);
|
||||||
|
graphics.drawLine(x1, y1, (int) xn, (int) yn);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case CONE: {
|
||||||
|
if (coordinates == null || coordinates.size() < 2) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
final int x1 = x0 + coordinates.get(0).getX();
|
||||||
|
final int y1 = y0 + coordinates.get(0).getY();
|
||||||
|
final int x2 = x0 + coordinates.get(1).getX();
|
||||||
|
final int y2 = y0 + coordinates.get(1).getY();
|
||||||
|
|
||||||
|
final Area2 area = WorldUtils2.pointsToArea(ImmutableCoordI2.create(x0, y0), ImmutableCoordI2.create(x1, y1), ImmutableCoordI2.create(x2, y2));
|
||||||
|
if (this.cull(area)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
graphics.setColor(this.color == null ? Color.WHITE : this.color.toAwtColor());
|
||||||
|
graphics.drawLine(x0, y0, x1, y1);
|
||||||
|
graphics.drawLine(x0, y0, x2, y2);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case DECORATIVE_BORDER:
|
||||||
|
case DECORATIVE_BORDER_FILLED: {
|
||||||
|
if (coordinates == null || coordinates.isEmpty()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
final int x1 = x0 + coordinates.get(0).getX();
|
||||||
|
final int y1 = y0 + coordinates.get(0).getY();
|
||||||
|
|
||||||
|
final Area2 area = WorldUtils2.pointsToArea(ImmutableCoordI2.create(x0, y0), ImmutableCoordI2.create(x1, y1));
|
||||||
|
if (this.cull(area)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int strength = this.customInt0 <= 0 ? 2 : this.customInt0;
|
||||||
|
|
||||||
|
if (this.effect == DrawEffectRenderingBoEffects.DECORATIVE_BORDER) {
|
||||||
|
ComponentRenderingSupport.drawDecorativeBorder(graphics, area.getPosition().getX(), area.getPosition().getY(), area.getDimension().getX(), area.getDimension().getY(), strength, this.color == null ? Color.WHITE : this.color.toAwtColor());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ComponentRenderingSupport.drawDecorativeBorderFilled(graphics, area.getPosition().getX(), area.getPosition().getY(), area.getDimension().getX(), area.getDimension().getY(), strength, this.color == null ? Color.WHITE : this.color.toAwtColor());
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case TEXT: {
|
||||||
|
if (this.customString0 == null || this.customString0.isEmpty()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
graphics.setColor(this.color.toAwtColor());
|
||||||
|
G2DDrawFont.renderText(graphics, 1.0, x0, y0, (int) (this.customInt0 * scaleFactor), this.customString0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new UnsupportedOperationException("Effect not implemented: " + this.effect.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean cull(final Area2 area) {
|
||||||
|
final Frame mainFrame = ((G2DRenderer) this.rendererData.getRenderer()).getMainFrame();
|
||||||
|
return !WorldUtils2.intersects(area, new Area2(ImmutableCoordI2.zero(), MutableCoordI2.create(mainFrame.getWidth(), mainFrame.getHeight())));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws Exception {
|
||||||
|
super.close();
|
||||||
|
|
||||||
|
this.relativeCoordinates = null;
|
||||||
|
this.effect = null;
|
||||||
|
this.customInt0 = 0;
|
||||||
|
this.customDouble0 = 0.0;
|
||||||
|
this.customString0 = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DrawEffectRenderingBo setEffect(final DrawEffectRenderingBoEffects effect) {
|
||||||
|
this.effect = effect;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DrawEffectRenderingBo setCustomInt0(final int customInt0) {
|
||||||
|
this.customInt0 = customInt0;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DrawEffectRenderingBo setCustomDouble0(final double customDouble0) {
|
||||||
|
this.customDouble0 = customDouble0;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DrawEffectRenderingBo setCustomString0(final String customString0) {
|
||||||
|
this.customString0 = customString0;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<CoordI2> getRelativeCoordinates() {
|
||||||
|
return this.relativeCoordinates;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DrawEffectRenderingBo setRelativeCoordinates(final List<CoordI2> polarCoordinates) {
|
||||||
|
this.relativeCoordinates = polarCoordinates;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
package de.extio.game_engine.renderer.g2d.bo.rendering;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Font;
|
||||||
|
import java.awt.Graphics;
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.Rectangle;
|
||||||
|
import java.awt.font.GlyphVector;
|
||||||
|
import java.awt.font.TextAttribute;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import de.extio.game_engine.renderer.model.bo.DrawFontRenderingBo;
|
||||||
|
import de.extio.game_engine.renderer.model.DrawFontRenderingBoTextAlignment;
|
||||||
|
import de.extio.game_engine.renderer.model.RenderingBoLayer;
|
||||||
|
import de.extio.game_engine.spatial2.model.CoordI2;
|
||||||
|
import de.extio.game_engine.spatial2.model.ImmutableCoordI2;
|
||||||
|
import de.extio.game_engine.spatial2.model.MutableCoordI2;
|
||||||
|
|
||||||
|
public class G2DDrawFont extends G2DAbstractRenderingBo implements DrawFontRenderingBo {
|
||||||
|
|
||||||
|
private final static float FONT_SIZE_MIN = 12.0F;
|
||||||
|
|
||||||
|
private final static int FONT_SIZE_DEFAULT = 14;
|
||||||
|
|
||||||
|
private final static Font baseFont;
|
||||||
|
|
||||||
|
static {
|
||||||
|
Font f;
|
||||||
|
try {
|
||||||
|
f = Font.createFont(Font.TRUETYPE_FONT, ClassLoader.getSystemClassLoader().getResourceAsStream("Roboto-Regular.ttf"));
|
||||||
|
}
|
||||||
|
catch (final Exception e) {
|
||||||
|
final Map<TextAttribute, Object> attributes = new HashMap<>();
|
||||||
|
attributes.put(TextAttribute.FAMILY, Font.SANS_SERIF);
|
||||||
|
attributes.put(TextAttribute.WEIGHT, TextAttribute.WEIGHT_SEMIBOLD);
|
||||||
|
f = Font.getFont(attributes);
|
||||||
|
}
|
||||||
|
baseFont = f;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String text;
|
||||||
|
|
||||||
|
private int size;
|
||||||
|
|
||||||
|
private DrawFontRenderingBoTextAlignment alignment = DrawFontRenderingBoTextAlignment.LEFT;
|
||||||
|
|
||||||
|
public G2DDrawFont() {
|
||||||
|
super(RenderingBoLayer.UI0_0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DrawFontRenderingBo setText(final String text) {
|
||||||
|
this.text = text;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DrawFontRenderingBo setSize(final int size) {
|
||||||
|
this.size = size;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DrawFontRenderingBo setAlignment(final DrawFontRenderingBoTextAlignment alignment) {
|
||||||
|
this.alignment = alignment;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render(final Graphics2D graphics, final double scaleFactor, final boolean force) {
|
||||||
|
if (this.text == null || this.text.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
graphics.setColor(this.color == null ? Color.WHITE : this.color.toAwtColor());
|
||||||
|
|
||||||
|
switch (this.alignment) {
|
||||||
|
case LEFT:
|
||||||
|
renderText(graphics, scaleFactor, this.x, this.y, this.size, this.text);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CENTER: {
|
||||||
|
final CoordI2 textDim = G2DDrawFont.getTextDimensions(this.text, graphics, this.size, 1.0);
|
||||||
|
renderText(graphics, textDim, scaleFactor, (this.width - textDim.getX()) / 2 + this.x, this.y, this.size, this.text);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case RIGHT: {
|
||||||
|
final CoordI2 textDim = G2DDrawFont.getTextDimensions(this.text, graphics, this.size, 1.0);
|
||||||
|
renderText(graphics, textDim, scaleFactor, this.width - textDim.getX() + this.x, this.y, this.size, this.text);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws Exception {
|
||||||
|
super.close();
|
||||||
|
this.text = null;
|
||||||
|
this.size = 0;
|
||||||
|
this.alignment = DrawFontRenderingBoTextAlignment.LEFT;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Font getFont(final double scaleFactor, final int size_) {
|
||||||
|
final int size = size_ == 0 ? FONT_SIZE_DEFAULT : size_;
|
||||||
|
final float sizeScaled = Math.max(FONT_SIZE_MIN, (float) (size * scaleFactor));
|
||||||
|
return baseFont.deriveFont(sizeScaled);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void renderText(final Graphics graphics, final double scaleFactor, final int x, final int y, final int size_, final String text) {
|
||||||
|
renderText(graphics, null, scaleFactor, x, y, size_, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void renderText(final Graphics graphics, final CoordI2 textDim_, final double scaleFactor, final int x, final int y, final int size_, final String text) {
|
||||||
|
if (text == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int size = size_ == 0 ? FONT_SIZE_DEFAULT : size_;
|
||||||
|
final float sizeScaled = Math.max(FONT_SIZE_MIN, (float) (size * scaleFactor));
|
||||||
|
final Font font = baseFont.deriveFont(sizeScaled);
|
||||||
|
CoordI2 textDim;
|
||||||
|
if (textDim_ == null) {
|
||||||
|
textDim = getTextDimensions(text, graphics, size_, scaleFactor);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
textDim = textDim_;
|
||||||
|
}
|
||||||
|
graphics.setFont(font);
|
||||||
|
graphics.drawString(text, (int) (x * scaleFactor), (int) (y * scaleFactor) + textDim.getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CoordI2 getTextDimensions(final String text, final Graphics graphics, final int size_, final double scaleFactor) {
|
||||||
|
if (text == null) {
|
||||||
|
return ImmutableCoordI2.one();
|
||||||
|
}
|
||||||
|
|
||||||
|
final int size = size_ == 0 ? FONT_SIZE_DEFAULT : size_;
|
||||||
|
final float sizeScaled = Math.max(FONT_SIZE_MIN, (float) (size * scaleFactor));
|
||||||
|
final Font font = baseFont.deriveFont(sizeScaled);
|
||||||
|
final GlyphVector gv = font.layoutGlyphVector(((Graphics2D) graphics).getFontRenderContext(), text.toCharArray(), 0, text.length(), Font.LAYOUT_LEFT_TO_RIGHT);
|
||||||
|
final Rectangle pixBounds = gv.getPixelBounds(((Graphics2D) graphics).getFontRenderContext(), 0, 0);
|
||||||
|
return MutableCoordI2.create(pixBounds.x + pixBounds.width, pixBounds.height - (int) pixBounds.getMaxY());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package de.extio.game_engine.renderer.g2d.bo.rendering;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import de.extio.game_engine.renderer.model.RenderingBoLayer;
|
||||||
|
|
||||||
|
public class G2DDrawFpsHistory extends G2DAbstractRenderingBo {
|
||||||
|
|
||||||
|
private Collection<Integer> history;
|
||||||
|
|
||||||
|
private final int[] thresholds = new int[4];
|
||||||
|
|
||||||
|
private int thresholdsFrameRate;
|
||||||
|
|
||||||
|
public G2DDrawFpsHistory() {
|
||||||
|
super(RenderingBoLayer.TOP);
|
||||||
|
}
|
||||||
|
|
||||||
|
public G2DDrawFpsHistory setHistory(final Collection<Integer> history) {
|
||||||
|
this.history = history;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render(final Graphics2D graphics, final double scaleFactor, final boolean force) {
|
||||||
|
if (this.thresholdsFrameRate != this.rendererData.getRendererControl().getFrameRate()) {
|
||||||
|
this.thresholdsFrameRate = this.rendererData.getRendererControl().getFrameRate();
|
||||||
|
this.thresholds[0] = (int) (1000.0 / this.rendererData.getRendererControl().getFrameRate() * 0.2);
|
||||||
|
this.thresholds[0] = (int) (1000.0 / this.rendererData.getRendererControl().getFrameRate() * 0.75);
|
||||||
|
this.thresholds[0] = (int) (1000.0 / this.rendererData.getRendererControl().getFrameRate() * 0.9);
|
||||||
|
this.thresholds[0] = (int) (1000.0 / this.rendererData.getRendererControl().getFrameRate());
|
||||||
|
}
|
||||||
|
|
||||||
|
final int x = (int) (this.x * scaleFactor);
|
||||||
|
final int y = (int) (this.y * scaleFactor);
|
||||||
|
int i = 0;
|
||||||
|
for (final Integer val : this.history) {
|
||||||
|
if (val == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final int val_ = Math.max(1, Math.min((int) (100 * scaleFactor), (int) (val.intValue() * scaleFactor)));
|
||||||
|
|
||||||
|
if (val_ < this.thresholds[0]) {
|
||||||
|
graphics.setColor(Color.DARK_GRAY);
|
||||||
|
}
|
||||||
|
else if (val_ < this.thresholds[1]) {
|
||||||
|
graphics.setColor(Color.GRAY);
|
||||||
|
}
|
||||||
|
else if (val_ < this.thresholds[2]) {
|
||||||
|
graphics.setColor(Color.YELLOW);
|
||||||
|
}
|
||||||
|
else if (val_ < this.thresholds[3]) {
|
||||||
|
graphics.setColor(Color.ORANGE);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
graphics.setColor(Color.RED);
|
||||||
|
}
|
||||||
|
|
||||||
|
graphics.drawLine(x + i, y, x + i++, y + val_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws Exception {
|
||||||
|
super.close();
|
||||||
|
this.history = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,223 @@
|
|||||||
|
package de.extio.game_engine.renderer.g2d.bo.rendering;
|
||||||
|
|
||||||
|
import java.awt.AlphaComposite;
|
||||||
|
import java.awt.Composite;
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
|
||||||
|
import de.extio.game_engine.renderer.g2d.G2DRenderer;
|
||||||
|
import de.extio.game_engine.renderer.model.bo.DrawImageRenderingBo;
|
||||||
|
import de.extio.game_engine.renderer.model.RenderingBo;
|
||||||
|
import de.extio.game_engine.renderer.model.RenderingBoLayer;
|
||||||
|
|
||||||
|
public class G2DDrawImage extends G2DAbstractRenderingBo implements DrawImageRenderingBo {
|
||||||
|
|
||||||
|
private static Map<String, CachedImage> IMAGE_CACHE = new HashMap<>();
|
||||||
|
|
||||||
|
private String resourceName;
|
||||||
|
|
||||||
|
private float transparency;
|
||||||
|
|
||||||
|
private byte[] imageData;
|
||||||
|
|
||||||
|
private int scaledX;
|
||||||
|
|
||||||
|
private int scaledY;
|
||||||
|
|
||||||
|
public G2DDrawImage() {
|
||||||
|
super(RenderingBoLayer.BACKGROUND1);
|
||||||
|
|
||||||
|
this.transparency = 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DrawImageRenderingBo setResourceName(final String resourceName) {
|
||||||
|
this.resourceName = resourceName;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DrawImageRenderingBo setTransparency(final float transparency) {
|
||||||
|
this.transparency = transparency;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DrawImageRenderingBo setImageData(final byte[] data) {
|
||||||
|
this.imageData = data;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("resource")
|
||||||
|
@Override
|
||||||
|
public void render(final Graphics2D graphics, final double scaleFactor, final boolean force) {
|
||||||
|
CachedImage cachedImage = IMAGE_CACHE.get(this.resourceName);
|
||||||
|
if (cachedImage == null) {
|
||||||
|
cachedImage = new CachedImage();
|
||||||
|
cachedImage.setName(this.resourceName);
|
||||||
|
|
||||||
|
if (this.imageData == null) {
|
||||||
|
try (InputStream stream = new FileInputStream(this.resourceName)) {
|
||||||
|
if (stream != null) {
|
||||||
|
cachedImage.setImage(ImageIO.read(stream));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (final IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
try (InputStream in = new ByteArrayInputStream(this.imageData)) {
|
||||||
|
cachedImage.setImage(ImageIO.read(in));
|
||||||
|
}
|
||||||
|
catch (final IOException e) {
|
||||||
|
LOGGER.error(e.getMessage(), e);
|
||||||
|
cachedImage.setImage(((G2DRenderer) this.rendererData.getRenderer()).getMainFrame().getGraphicsConfiguration().createCompatibleImage(1, 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IMAGE_CACHE.put(this.resourceName, cachedImage);
|
||||||
|
LOGGER.debug("Added image to cache: " + cachedImage.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.transparency < 1.0f) {
|
||||||
|
final Composite currentAlphaComposite = graphics.getComposite();
|
||||||
|
try {
|
||||||
|
AlphaComposite ac = cachedImage.getAlphaComposite();
|
||||||
|
if (ac == null) {
|
||||||
|
ac = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, this.transparency);
|
||||||
|
cachedImage.setAlphaComposite(ac);
|
||||||
|
}
|
||||||
|
|
||||||
|
graphics.setComposite(ac);
|
||||||
|
this.doDraw(graphics, cachedImage, scaleFactor);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
graphics.setComposite(currentAlphaComposite);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (this.transparency == 1.0f) {
|
||||||
|
this.doDraw(graphics, cachedImage, scaleFactor);
|
||||||
|
}
|
||||||
|
|
||||||
|
cachedImage.setUsed(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws Exception {
|
||||||
|
super.close();
|
||||||
|
|
||||||
|
this.resourceName = null;
|
||||||
|
this.transparency = 1.0f;
|
||||||
|
this.imageData = null;
|
||||||
|
this.scaledX = 0;
|
||||||
|
this.scaledY = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void closeStatic() {
|
||||||
|
final Iterator<CachedImage> it = IMAGE_CACHE.values().iterator();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
final CachedImage cachedImage = it.next();
|
||||||
|
if (!cachedImage.isUsed()) {
|
||||||
|
LOGGER.debug("Removed image from cache: " + cachedImage.getName());
|
||||||
|
|
||||||
|
cachedImage.close();
|
||||||
|
it.remove();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cachedImage.setUsed(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doDraw(final Graphics2D graphics, final CachedImage cachedImage, final double scaleFactor) {
|
||||||
|
int x, y;
|
||||||
|
if (this.scaledX != 0 || this.scaledY != 0) {
|
||||||
|
x = this.scaledX;
|
||||||
|
y = this.scaledY;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
x = (int) (this.x * scaleFactor);
|
||||||
|
y = (int) (this.y * scaleFactor);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.width == 0 && this.height == 0) {
|
||||||
|
graphics.drawImage(cachedImage.getImage(), x, y, null);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
graphics.drawImage(cachedImage.getImage(), x, y, (int) (this.width * scaleFactor), (int) (this.height * scaleFactor), null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class CachedImage implements AutoCloseable {
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
private BufferedImage image;
|
||||||
|
|
||||||
|
private boolean used;
|
||||||
|
|
||||||
|
private AlphaComposite alphaComposite;
|
||||||
|
|
||||||
|
public boolean isUsed() {
|
||||||
|
return this.used;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUsed(final boolean used) {
|
||||||
|
this.used = used;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BufferedImage getImage() {
|
||||||
|
return this.image;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(final String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setImage(final BufferedImage image) {
|
||||||
|
this.image = image;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AlphaComposite getAlphaComposite() {
|
||||||
|
return this.alphaComposite;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAlphaComposite(final AlphaComposite alphaComposite) {
|
||||||
|
this.alphaComposite = alphaComposite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
if (this.image != null) {
|
||||||
|
this.image.flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
final StringBuilder builder = new StringBuilder();
|
||||||
|
builder.append("CachedImage [name=");
|
||||||
|
builder.append(this.name);
|
||||||
|
builder.append(", used=");
|
||||||
|
builder.append(this.used);
|
||||||
|
builder.append("]");
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package de.extio.game_engine.renderer.g2d.bo.rendering;
|
||||||
|
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
|
||||||
|
import de.extio.game_engine.renderer.model.bo.DrawTestRenderingBo;
|
||||||
|
import de.extio.game_engine.renderer.model.RenderingBoLayer;
|
||||||
|
|
||||||
|
public class G2DDrawTest extends G2DAbstractRenderingBo implements DrawTestRenderingBo {
|
||||||
|
|
||||||
|
// private static BufferedImage TILE;
|
||||||
|
//
|
||||||
|
// private static BufferedImage TILE2;
|
||||||
|
|
||||||
|
static {
|
||||||
|
/*ResourceFileManager.load(ResourceFileManager.RESOURCE_FILE_RESOURCES, (name, stream) -> {
|
||||||
|
try {
|
||||||
|
TILE = ImageIO.read(stream);
|
||||||
|
|
||||||
|
// Right
|
||||||
|
//TILE = tileImg;
|
||||||
|
|
||||||
|
// Left
|
||||||
|
// AffineTransform tx = AffineTransform.getRotateInstance(Math.toRadians(180));
|
||||||
|
// tx.translate(-TILE.getWidth(), -TILE.getHeight());
|
||||||
|
// AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
|
||||||
|
// TILE2 = op.filter(TILE, null);
|
||||||
|
|
||||||
|
// Bottom
|
||||||
|
// AffineTransform tx = AffineTransform.getRotateInstance(Math.toRadians(90));
|
||||||
|
// tx.translate(0, -TILE.getHeight());
|
||||||
|
// AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
|
||||||
|
// TILE2 = op.filter(TILE, null);
|
||||||
|
|
||||||
|
|
||||||
|
// Top
|
||||||
|
// AffineTransform tx = AffineTransform.getRotateInstance(Math.toRadians(270));
|
||||||
|
// tx.translate(-TILE.getWidth(), 0);
|
||||||
|
// AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
|
||||||
|
// TILE2 = op.filter(TILE, null);
|
||||||
|
}
|
||||||
|
catch (final IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}, "gfx/tilesets/0.png");*/
|
||||||
|
}
|
||||||
|
|
||||||
|
public G2DDrawTest() {
|
||||||
|
super(RenderingBoLayer.UI0_1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render(final Graphics2D graphics, final double scaleFactor, final boolean force) {
|
||||||
|
// graphics.drawImage(TILE,
|
||||||
|
// 600,
|
||||||
|
// 200,
|
||||||
|
// null);
|
||||||
|
// graphics.drawImage(TILE2,
|
||||||
|
// 600,
|
||||||
|
// 200,
|
||||||
|
// null);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package de.extio.game_engine.renderer.g2d.bo.rendering;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
|
||||||
|
import de.extio.game_engine.renderer.g2d.control.components.ComponentRenderingSupport;
|
||||||
|
import de.extio.game_engine.renderer.model.ImmutableRgbaColor;
|
||||||
|
import de.extio.game_engine.renderer.model.RenderingBoLayer;
|
||||||
|
import de.extio.game_engine.renderer.model.RgbaColor;
|
||||||
|
import de.extio.game_engine.renderer.model.bo.DrawWindowRenderingBo;
|
||||||
|
|
||||||
|
public class G2DDrawWindow extends G2DAbstractRenderingBo implements DrawWindowRenderingBo {
|
||||||
|
|
||||||
|
private static final RgbaColor COLOR_WINDOW = new ImmutableRgbaColor(ComponentRenderingSupport.COLOR_COMPONENT_BORDER1.getRed(), ComponentRenderingSupport.COLOR_COMPONENT_BORDER1.getGreen(), ComponentRenderingSupport.COLOR_COMPONENT_BORDER1.getBlue());
|
||||||
|
|
||||||
|
private boolean thickBorder;
|
||||||
|
|
||||||
|
public G2DDrawWindow() {
|
||||||
|
super(RenderingBoLayer.UI0_BGR);
|
||||||
|
|
||||||
|
this.color = COLOR_WINDOW;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DrawWindowRenderingBo setThickBorder(final boolean thickBorder) {
|
||||||
|
this.thickBorder = thickBorder;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render(final Graphics2D graphics, final double scaleFactor, final boolean force) {
|
||||||
|
final int sx = (int) (this.x * scaleFactor);
|
||||||
|
final int sy = (int) (this.y * scaleFactor);
|
||||||
|
final int sw = (int) (this.width * scaleFactor);
|
||||||
|
final int sh = (int) (this.height * scaleFactor);
|
||||||
|
final int strength = Math.max(2, (int) (3 * scaleFactor));
|
||||||
|
if (this.color == null) {
|
||||||
|
this.color = COLOR_WINDOW;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Color cMain = this.color.toAwtColor();
|
||||||
|
final RgbaColor rgbaDarker = new RgbaColor(cMain);
|
||||||
|
rgbaDarker.setR(rgbaDarker.getR() / 2);
|
||||||
|
rgbaDarker.setG(rgbaDarker.getG() / 2);
|
||||||
|
rgbaDarker.setB(rgbaDarker.getB() / 2);
|
||||||
|
final Color cDarker = rgbaDarker.toAwtColor();
|
||||||
|
|
||||||
|
if (this.thickBorder) {
|
||||||
|
final int borderStrength = strength * 2;
|
||||||
|
graphics.setColor(cMain);
|
||||||
|
graphics.fillRect(sx + strength * 2, sy + strength * 2, sw - strength * 4, borderStrength);
|
||||||
|
graphics.setColor(cDarker);
|
||||||
|
graphics.fillRect(sx + strength * 2, sy + strength * 2 + borderStrength, sw - strength * 4, strength);
|
||||||
|
}
|
||||||
|
|
||||||
|
ComponentRenderingSupport.drawDecorativeBorder(graphics, sx, sy, sw, sh, strength, cDarker);
|
||||||
|
ComponentRenderingSupport.drawDecorativeBorderFilled(graphics, sx + strength, sy + strength, sw - strength * 2, sh - strength * 2, strength, cMain);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws Exception {
|
||||||
|
super.close();
|
||||||
|
this.color = COLOR_WINDOW;
|
||||||
|
this.thickBorder = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
package de.extio.game_engine.renderer.g2d.bo.rendering;
|
||||||
|
|
||||||
|
import java.awt.AlphaComposite;
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Composite;
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.Transparency;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import de.extio.game_engine.renderer.g2d.G2DRenderer;
|
||||||
|
import de.extio.game_engine.renderer.model.bo.DrawWindowRenderingBo;
|
||||||
|
import de.extio.game_engine.renderer.model.ImmutableRgbaColor;
|
||||||
|
import de.extio.game_engine.renderer.model.RenderingBoLayer;
|
||||||
|
import de.extio.game_engine.renderer.model.RgbaColor;
|
||||||
|
|
||||||
|
public class G2DDrawWindow2 extends G2DAbstractRenderingBo implements DrawWindowRenderingBo {
|
||||||
|
|
||||||
|
private static final Map<Long, G2DWindowCacheEntry> CACHE = new HashMap<>();
|
||||||
|
|
||||||
|
private static final RgbaColor COLOR_WINDOW_BGR = new ImmutableRgbaColor(119, 119, 189);
|
||||||
|
|
||||||
|
private static double LAST_SCALEFACTOR;
|
||||||
|
|
||||||
|
private static AlphaComposite alphaComposite;
|
||||||
|
|
||||||
|
private boolean thickBorder;
|
||||||
|
|
||||||
|
private Color awtColor;
|
||||||
|
|
||||||
|
public G2DDrawWindow2() {
|
||||||
|
super(RenderingBoLayer.UI0_BGR);
|
||||||
|
|
||||||
|
this.color = COLOR_WINDOW_BGR;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DrawWindowRenderingBo setThickBorder(final boolean thickBorder) {
|
||||||
|
this.thickBorder = thickBorder;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render(final Graphics2D graphics, final double scaleFactor, final boolean force) {
|
||||||
|
this.awtColor = this.color == null ? COLOR_WINDOW_BGR.toAwtColor() : this.color.toAwtColor();
|
||||||
|
|
||||||
|
if (scaleFactor != LAST_SCALEFACTOR) {
|
||||||
|
CACHE.clear();
|
||||||
|
LAST_SCALEFACTOR = scaleFactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Long key = Long.valueOf((long) this.width | ((long) this.height << 12) | ((this.thickBorder ? 1L : 0L) << 24) | ((long) this.awtColor.getRGB() << 32));
|
||||||
|
G2DWindowCacheEntry entry = CACHE.get(key);
|
||||||
|
if (entry == null) {
|
||||||
|
final BufferedImage img = ((G2DRenderer) this.rendererData.getRenderer()).getMainFrame().getGraphicsConfiguration().createCompatibleImage((int) (this.width * scaleFactor), (int) (this.height * scaleFactor), Transparency.TRANSLUCENT);
|
||||||
|
this.paintWindow(img, scaleFactor);
|
||||||
|
|
||||||
|
entry = new G2DWindowCacheEntry();
|
||||||
|
entry.setImage(img);
|
||||||
|
CACHE.put(key, entry);
|
||||||
|
|
||||||
|
}
|
||||||
|
entry.setUsed(true);
|
||||||
|
|
||||||
|
final Composite currentAlphaComposite = graphics.getComposite();
|
||||||
|
try {
|
||||||
|
if (alphaComposite == null) {
|
||||||
|
alphaComposite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.6F);
|
||||||
|
}
|
||||||
|
|
||||||
|
graphics.setComposite(alphaComposite);
|
||||||
|
graphics.drawImage(entry.getImage(), (int) (this.x * scaleFactor), (int) (this.y * scaleFactor), null);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
graphics.setComposite(currentAlphaComposite);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws Exception {
|
||||||
|
super.close();
|
||||||
|
this.color = COLOR_WINDOW_BGR;
|
||||||
|
this.thickBorder = false;
|
||||||
|
this.awtColor = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void closeStatic() {
|
||||||
|
final Iterator<G2DWindowCacheEntry> it = CACHE.values().iterator();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
final G2DWindowCacheEntry entry = it.next();
|
||||||
|
if (!entry.isUsed()) {
|
||||||
|
entry.close();
|
||||||
|
it.remove();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
entry.setUsed(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void paintWindow(final BufferedImage img, final double scaleFactor) {
|
||||||
|
final Graphics2D imgGraphics = img.createGraphics();
|
||||||
|
try {
|
||||||
|
final int swidth = (int) (this.width * scaleFactor);
|
||||||
|
final int sheight = (int) (this.height * scaleFactor);
|
||||||
|
final float[] baseColor = new float[3];
|
||||||
|
this.awtColor.getRGBColorComponents(baseColor);
|
||||||
|
Color.RGBtoHSB((int) (baseColor[0] * 255), (int) (baseColor[1] * 255), (int) (baseColor[2] * 255), baseColor);
|
||||||
|
|
||||||
|
final int size = (int) (5 * scaleFactor);
|
||||||
|
|
||||||
|
imgGraphics.setColor(Color.BLACK);
|
||||||
|
imgGraphics.fillRect(0, size, swidth, sheight - size * 3);
|
||||||
|
imgGraphics.fillRect(size, sheight - size * 2, swidth - size * 2, size);
|
||||||
|
|
||||||
|
imgGraphics.setColor(Color.getHSBColor(baseColor[0], baseColor[1], baseColor[2]));
|
||||||
|
if (this.thickBorder) {
|
||||||
|
imgGraphics.fillRect(0, size * 2, size, sheight - size * 4);
|
||||||
|
imgGraphics.fillRect(swidth - size, size * 2, swidth, sheight - size * 4);
|
||||||
|
imgGraphics.fillRect(0, size * 2, swidth, size);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
imgGraphics.fillRect(0, 0, size, sheight - size * 2);
|
||||||
|
imgGraphics.fillRect(swidth - size, 0, swidth, sheight - size * 2);
|
||||||
|
imgGraphics.fillRect(0, 0, swidth, size);
|
||||||
|
}
|
||||||
|
imgGraphics.fillRect(size * 2, sheight - size, swidth - size * 4, size);
|
||||||
|
imgGraphics.fillPolygon(new int[] { 0, size * 2, size * 2, size }, new int[] { sheight - size * 2, sheight, sheight - size, sheight - size * 2 }, 4);
|
||||||
|
imgGraphics.fillPolygon(new int[] { swidth, swidth - size * 2, swidth - size * 2, swidth - size }, new int[] { sheight - size * 2, sheight, sheight - size, sheight - size * 2 }, 4);
|
||||||
|
|
||||||
|
if (this.thickBorder) {
|
||||||
|
for (int i = 0; i < size * 2; i++) {
|
||||||
|
imgGraphics.setColor(Color.getHSBColor(baseColor[0], baseColor[1], baseColor[2] * ((0.5F / (size * 2) * i) + 0.5F)));
|
||||||
|
imgGraphics.drawLine(0, i, swidth, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
imgGraphics.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class G2DWindowCacheEntry implements AutoCloseable {
|
||||||
|
|
||||||
|
private BufferedImage image;
|
||||||
|
|
||||||
|
private boolean used;
|
||||||
|
|
||||||
|
public BufferedImage getImage() {
|
||||||
|
return this.image;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setImage(final BufferedImage image) {
|
||||||
|
this.image = image;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isUsed() {
|
||||||
|
return this.used;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUsed(final boolean used) {
|
||||||
|
this.used = used;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
if (this.image != null) {
|
||||||
|
this.image.flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package de.extio.game_engine.renderer.g2d.control;
|
||||||
|
|
||||||
|
public interface G2DControlHasExclusiveKeyEvent {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,359 @@
|
|||||||
|
package de.extio.game_engine.renderer.g2d.control;
|
||||||
|
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import de.extio.game_engine.renderer.g2d.control.impl.G2DBaseControlImpl;
|
||||||
|
import de.extio.game_engine.renderer.g2d.control.impl.G2DButtonControlImpl;
|
||||||
|
import de.extio.game_engine.renderer.g2d.control.impl.G2DLabelControlImpl;
|
||||||
|
import de.extio.game_engine.renderer.g2d.control.impl.G2DSetFocusControl;
|
||||||
|
import de.extio.game_engine.renderer.g2d.control.impl.G2DSliderControlImpl;
|
||||||
|
import de.extio.game_engine.renderer.g2d.control.impl.G2DSwitchControlImpl;
|
||||||
|
import de.extio.game_engine.renderer.g2d.control.impl.G2DTableControlImpl;
|
||||||
|
import de.extio.game_engine.renderer.g2d.control.impl.G2DTextfieldControlImpl2;
|
||||||
|
import de.extio.game_engine.renderer.g2d.control.impl.G2DToggleButtonControlImpl;
|
||||||
|
import de.extio.game_engine.renderer.g2d.control.impl.G2DTooltipControl;
|
||||||
|
import de.extio.game_engine.renderer.g2d.control.impl.G2DWindowCloseButtonControlImpl;
|
||||||
|
import de.extio.game_engine.renderer.g2d.bo.rendering.G2DAbstractRenderingBo;
|
||||||
|
import de.extio.game_engine.renderer.model.bo.ControlRenderingBo;
|
||||||
|
import de.extio.game_engine.renderer.model.DrawFontRenderingBoTextAlignment;
|
||||||
|
import de.extio.game_engine.renderer.model.RenderingBo;
|
||||||
|
import de.extio.game_engine.renderer.model.RenderingBoLayer;
|
||||||
|
import de.extio.game_engine.renderer.model.RgbaColor;
|
||||||
|
import de.extio.game_engine.spatial2.model.CoordI2;
|
||||||
|
import de.extio.game_engine.spatial2.model.ImmutableCoordI2;
|
||||||
|
|
||||||
|
public class G2DDrawControl extends G2DAbstractRenderingBo implements ControlRenderingBo {
|
||||||
|
|
||||||
|
private final static Logger LOGGER = LoggerFactory.getLogger(G2DDrawControl.class);
|
||||||
|
|
||||||
|
private static Map<String, BaseControl> CACHED_CONTROLS = new HashMap<>();
|
||||||
|
|
||||||
|
public static Map<String, List<BaseControl>> CONTROL_GROUPS = new HashMap<>();
|
||||||
|
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
private Class<? extends BaseControl> clazz;
|
||||||
|
|
||||||
|
private String caption;
|
||||||
|
|
||||||
|
private String controlGroup;
|
||||||
|
|
||||||
|
private int fontSize = 16;
|
||||||
|
|
||||||
|
private Object customData;
|
||||||
|
|
||||||
|
private Object customData2;
|
||||||
|
|
||||||
|
private Object customData3;
|
||||||
|
|
||||||
|
private Object customData4;
|
||||||
|
|
||||||
|
private boolean visible;
|
||||||
|
|
||||||
|
private boolean enabled = true;
|
||||||
|
|
||||||
|
private String tooltip;
|
||||||
|
|
||||||
|
public G2DDrawControl() {
|
||||||
|
super(RenderingBoLayer.UI0_0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ControlRenderingBo setId(final String id) {
|
||||||
|
this.id = id;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return this.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ControlRenderingBo setType(final Class<? extends BaseControl> clazz) {
|
||||||
|
this.clazz = clazz;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ControlRenderingBo setCaption(final String caption) {
|
||||||
|
this.caption = caption;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ControlRenderingBo setControlGroup(final String controlGroup) {
|
||||||
|
this.controlGroup = controlGroup;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ControlRenderingBo setFontSize(final int size) {
|
||||||
|
this.fontSize = size;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ControlRenderingBo setCustomData(final Object data) {
|
||||||
|
this.customData = data;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ControlRenderingBo setCustomData2(final Object data) {
|
||||||
|
this.customData2 = data;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ControlRenderingBo setCustomData3(final Object data) {
|
||||||
|
this.customData3 = data;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ControlRenderingBo setCustomData4(final Object data) {
|
||||||
|
this.customData4 = data;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ControlRenderingBo setVisible(final boolean visible) {
|
||||||
|
this.visible = visible;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ControlRenderingBo setEnabled(final boolean enabled) {
|
||||||
|
this.enabled = enabled;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ControlRenderingBo setTooltip(final String tooltip) {
|
||||||
|
this.tooltip = tooltip;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render(final Graphics2D graphics, final double scaleFactor, final boolean force) {
|
||||||
|
G2DBaseControlImpl control = (G2DBaseControlImpl) CACHED_CONTROLS.get(this.id);
|
||||||
|
if (control == null) {
|
||||||
|
if (this.clazz == ButtonControl.class) {
|
||||||
|
control = new G2DButtonControlImpl();
|
||||||
|
}
|
||||||
|
else if (this.clazz == LabelControl.class) {
|
||||||
|
control = new G2DLabelControlImpl();
|
||||||
|
}
|
||||||
|
else if (this.clazz == TextfieldControl.class) {
|
||||||
|
// control = new G2DTextfieldControlImpl();
|
||||||
|
control = new G2DTextfieldControlImpl2();
|
||||||
|
}
|
||||||
|
else if (this.clazz == ToggleButtonControl.class) {
|
||||||
|
control = new G2DToggleButtonControlImpl();
|
||||||
|
}
|
||||||
|
else if (this.clazz == SwitchControl.class) {
|
||||||
|
control = new G2DSwitchControlImpl();
|
||||||
|
}
|
||||||
|
else if (this.clazz == WindowCloseButtonControl.class) {
|
||||||
|
control = new G2DWindowCloseButtonControlImpl();
|
||||||
|
}
|
||||||
|
else if (this.clazz == SliderControl.class) {
|
||||||
|
control = new G2DSliderControlImpl();
|
||||||
|
}
|
||||||
|
else if (this.clazz == TableControl.class) {
|
||||||
|
control = new G2DTableControlImpl();
|
||||||
|
}
|
||||||
|
else if (this.clazz == SetFocusControl.class) {
|
||||||
|
control = new G2DSetFocusControl();
|
||||||
|
}
|
||||||
|
else if (this.clazz == TooltipControl.class) {
|
||||||
|
control = new G2DTooltipControl();
|
||||||
|
}
|
||||||
|
else if (this.clazz == null) {
|
||||||
|
throw new IllegalArgumentException("G2DDrawControl: control type not specified");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new UnsupportedOperationException("Not implemented " + this.clazz.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
control.setScaleFactor(scaleFactor)
|
||||||
|
.setControlGroup(this.controlGroup)
|
||||||
|
.setRendererData(this.rendererData)
|
||||||
|
.setId(this.id);
|
||||||
|
if (this.clazz == TextfieldControl.class && this.customData instanceof Boolean) {
|
||||||
|
((TextfieldControl) control).setMultiLine(((Boolean) this.customData).booleanValue());
|
||||||
|
}
|
||||||
|
this.setControlProps(control, scaleFactor);
|
||||||
|
|
||||||
|
control.build();
|
||||||
|
|
||||||
|
CACHED_CONTROLS.put(this.id, control);
|
||||||
|
if (this.controlGroup != null && !this.controlGroup.isEmpty()) {
|
||||||
|
List<BaseControl> controls = CONTROL_GROUPS.get(this.controlGroup);
|
||||||
|
if (controls == null) {
|
||||||
|
controls = new ArrayList<>();
|
||||||
|
CONTROL_GROUPS.put(this.controlGroup, controls);
|
||||||
|
}
|
||||||
|
|
||||||
|
controls.add(control);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.debug("Added control " + control.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setControlProps(control, scaleFactor);
|
||||||
|
control.setMainFrameGraphics(graphics);
|
||||||
|
control.setInUse(true);
|
||||||
|
control.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private void setControlProps(final G2DBaseControlImpl control, final double scaleFactor) {
|
||||||
|
control.setScaleFactor(scaleFactor);
|
||||||
|
control.setX((int) (this.x * scaleFactor));
|
||||||
|
control.setY((int) (this.y * scaleFactor));
|
||||||
|
control.setFontSize(this.fontSize);
|
||||||
|
control.setWidth((int) (this.width * scaleFactor));
|
||||||
|
control.setHeight((int) (this.height * scaleFactor));
|
||||||
|
control.setVisible(this.visible);
|
||||||
|
control.setEnabled(this.enabled);
|
||||||
|
control.setCaption(this.caption);
|
||||||
|
control.setTooltip(this.tooltip);
|
||||||
|
|
||||||
|
if (this.clazz == LabelControl.class) {
|
||||||
|
((LabelControl) control).setForegroundColor(this.color);
|
||||||
|
if (this.customData instanceof RgbaColor) {
|
||||||
|
((LabelControl) control).setBackgroundColor((RgbaColor) this.customData);
|
||||||
|
}
|
||||||
|
if (this.customData2 instanceof DrawFontRenderingBoTextAlignment) {
|
||||||
|
((LabelControl) control).setTextAlignment((DrawFontRenderingBoTextAlignment) this.customData2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (this.clazz == ToggleButtonControl.class) {
|
||||||
|
((ButtonControl) control).setBackgroundColor(this.color);
|
||||||
|
if (this.customData instanceof Boolean) {
|
||||||
|
((ToggleButtonControl) control).setToggled(((Boolean) this.customData).booleanValue());
|
||||||
|
}
|
||||||
|
if (this.customData2 instanceof String) {
|
||||||
|
((ButtonControl) control).setIconResourceName((String) this.customData2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (this.clazz == SwitchControl.class) {
|
||||||
|
((ButtonControl) control).setBackgroundColor(this.color);
|
||||||
|
if (this.customData instanceof Boolean) {
|
||||||
|
((SwitchControl) control).setToggled(((Boolean) this.customData).booleanValue());
|
||||||
|
}
|
||||||
|
if (this.customData2 instanceof String) {
|
||||||
|
((ButtonControl) control).setIconResourceName((String) this.customData2);
|
||||||
|
}
|
||||||
|
if (this.customData4 instanceof Boolean) {
|
||||||
|
((SwitchControl) control).setDrawBorder(((Boolean) this.customData4).booleanValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (this.clazz == ButtonControl.class) {
|
||||||
|
((ButtonControl) control).setBackgroundColor(this.color);
|
||||||
|
if (this.customData2 instanceof String) {
|
||||||
|
((ButtonControl) control).setIconResourceName((String) this.customData2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (this.clazz == WindowCloseButtonControl.class) {
|
||||||
|
((ButtonControl) control).setBackgroundColor(this.color);
|
||||||
|
if (this.customData instanceof Boolean) {
|
||||||
|
((WindowCloseButtonControl) control).setThickBorder(((Boolean) this.customData).booleanValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (this.clazz == SliderControl.class) {
|
||||||
|
((SliderControl) control).setColor(this.color);
|
||||||
|
if (this.customData instanceof Boolean) {
|
||||||
|
((SliderControl) control).setHorizontal(((Boolean) this.customData).booleanValue());
|
||||||
|
}
|
||||||
|
if (this.customData2 instanceof Double) {
|
||||||
|
((SliderControl) control).setValue(((Double) this.customData2).doubleValue());
|
||||||
|
}
|
||||||
|
if (this.customData3 instanceof Double) {
|
||||||
|
((SliderControl) control).setValue2(((Double) this.customData3).doubleValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (this.clazz == TableControl.class) {
|
||||||
|
if (this.customData instanceof List) {
|
||||||
|
((TableControl) control).setData((List<Object>) this.customData);
|
||||||
|
}
|
||||||
|
if (this.customData2 instanceof Integer) {
|
||||||
|
((TableControl) control).setRows((Integer) this.customData2);
|
||||||
|
}
|
||||||
|
if (this.customData3 instanceof Long) {
|
||||||
|
((TableControl) control).setVersion((Long) this.customData3);
|
||||||
|
}
|
||||||
|
if (this.customData4 instanceof Boolean) { // This parameter could be changed to a more generic way to set column sizing
|
||||||
|
((TableControl) control).setFirstColDoubleSize((Boolean) this.customData4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (this.clazz == SetFocusControl.class) {
|
||||||
|
if (this.customData instanceof String) {
|
||||||
|
((SetFocusControl) control).setFocusId((String) this.customData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (this.clazz == TextfieldControl.class) {
|
||||||
|
((TextfieldControl) control).setBackgroundColor(this.color);
|
||||||
|
if (this.customData2 instanceof Boolean) {
|
||||||
|
((TextfieldControl) control).setReadonly((Boolean) this.customData2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws Exception {
|
||||||
|
super.close();
|
||||||
|
|
||||||
|
this.id = null;
|
||||||
|
this.clazz = null;
|
||||||
|
this.caption = null;
|
||||||
|
this.controlGroup = null;
|
||||||
|
this.fontSize = 16;
|
||||||
|
this.customData = null;
|
||||||
|
this.customData2 = null;
|
||||||
|
this.customData3 = null;
|
||||||
|
this.customData4 = null;
|
||||||
|
this.visible = false;
|
||||||
|
this.enabled = true;
|
||||||
|
this.tooltip = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void closeStatic() {
|
||||||
|
final Iterator<BaseControl> it = CACHED_CONTROLS.values().iterator();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
final BaseControl control = it.next();
|
||||||
|
if (control.getInUse()) {
|
||||||
|
control.setInUse(false);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (control.getControlGroup() != null && !control.getControlGroup().isEmpty()) {
|
||||||
|
final List<BaseControl> controls = CONTROL_GROUPS.get(control.getControlGroup());
|
||||||
|
if (controls != null) {
|
||||||
|
controls.remove(control);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
control.close();
|
||||||
|
it.remove();
|
||||||
|
LOGGER.debug("Removed control " + control.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void reset() {
|
||||||
|
CACHED_CONTROLS.values().forEach(control -> control.close());
|
||||||
|
CACHED_CONTROLS.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package de.extio.game_engine.renderer.g2d.control;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
|
||||||
|
import de.extio.game_engine.renderer.g2d.bo.rendering.G2DAbstractRenderingBo;
|
||||||
|
import de.extio.game_engine.renderer.g2d.bo.rendering.G2DDrawFont;
|
||||||
|
import de.extio.game_engine.renderer.model.RenderingBoLayer;
|
||||||
|
import de.extio.game_engine.renderer.model.RgbaColor;
|
||||||
|
import de.extio.game_engine.spatial2.model.CoordI2;
|
||||||
|
|
||||||
|
public class G2DDrawControlTooltip extends G2DAbstractRenderingBo {
|
||||||
|
|
||||||
|
public static record TooltipRecord(CoordI2 position, String text) {}
|
||||||
|
|
||||||
|
public static TooltipRecord TOOLTIP = null;
|
||||||
|
|
||||||
|
private static Color BACKGROUND = (new RgbaColor(0, 0, 0, 150)).toAwtColor();
|
||||||
|
|
||||||
|
public G2DDrawControlTooltip() {
|
||||||
|
super(RenderingBoLayer.TOP);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render(final Graphics2D graphics, final double scaleFactor, final boolean force) {
|
||||||
|
if (TOOLTIP == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final CoordI2 textDim = G2DDrawFont.getTextDimensions(TOOLTIP.text(), graphics, 14, scaleFactor);
|
||||||
|
|
||||||
|
graphics.setColor(BACKGROUND);
|
||||||
|
graphics.fillRect(TOOLTIP.position().getX(), TOOLTIP.position().getY(), textDim.getX() + 20, textDim.getY() + 20);
|
||||||
|
|
||||||
|
graphics.setColor(Color.WHITE);
|
||||||
|
G2DDrawFont.renderText(graphics, scaleFactor, (int) ((TOOLTIP.position().getX() + 10) / scaleFactor), (int) ((TOOLTIP.position().getY() + 10) / scaleFactor), 14, TOOLTIP.text());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void closeStatic() {
|
||||||
|
TOOLTIP = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package de.extio.game_engine.renderer.g2d.control.components;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
|
||||||
|
public class ComponentRenderingSupport {
|
||||||
|
|
||||||
|
public final static record HSB(float h, float s, float b) {}
|
||||||
|
|
||||||
|
public final static HSB HSB_COMPONENT_BORDER0 = new HSB(0.71f, 0.12f, 0.50f);
|
||||||
|
|
||||||
|
public final static Color COLOR_COMPONENT_BORDER0 = Color.getHSBColor(HSB_COMPONENT_BORDER0.h(), HSB_COMPONENT_BORDER0.s(), HSB_COMPONENT_BORDER0.b());
|
||||||
|
|
||||||
|
public final static HSB HSB_COMPONENT_BORDER1 = new HSB(0.71f, 0.12f, 0.80f);
|
||||||
|
|
||||||
|
public final static Color COLOR_COMPONENT_BORDER1 = Color.getHSBColor(HSB_COMPONENT_BORDER1.h(), HSB_COMPONENT_BORDER1.s(), HSB_COMPONENT_BORDER1.b());
|
||||||
|
|
||||||
|
public final static HSB HSB_COMPONENT_BORDER1_DIS = new HSB(0.71f, 0.15f, 0.20f);
|
||||||
|
|
||||||
|
public final static Color COLOR_COMPONENT_BORDER1_DIS = Color.getHSBColor(HSB_COMPONENT_BORDER1_DIS.h(), HSB_COMPONENT_BORDER1_DIS.s(), HSB_COMPONENT_BORDER1_DIS.b());
|
||||||
|
|
||||||
|
public final static HSB HSB_COMPONENT_BGR = new HSB(0.71f, 0.92f, 0.30f);
|
||||||
|
|
||||||
|
public final static Color COLOR_COMPONENT_BGR = Color.getHSBColor(HSB_COMPONENT_BGR.h(), HSB_COMPONENT_BGR.s(), HSB_COMPONENT_BGR.b());
|
||||||
|
|
||||||
|
public final static HSB HSB_COMPONENT_SELECTED_0 = new HSB(0.199f, 0.83f, 0.50f);
|
||||||
|
|
||||||
|
public final static Color COLOR_COMPONENT_SELECTED_0 = Color.getHSBColor(HSB_COMPONENT_SELECTED_0.h(), HSB_COMPONENT_SELECTED_0.s(), HSB_COMPONENT_SELECTED_0.b());
|
||||||
|
|
||||||
|
public final static HSB HSB_COMPONENT_SELECTED_1 = new HSB(0.199f, 0.83f, 0.90f);
|
||||||
|
|
||||||
|
public final static Color COLOR_COMPONENT_SELECTED_1 = Color.getHSBColor(HSB_COMPONENT_SELECTED_1.h(), HSB_COMPONENT_SELECTED_1.s(), HSB_COMPONENT_SELECTED_1.b());
|
||||||
|
|
||||||
|
public final static Color COLOR_WINDOW_BGR = new Color(0, 0, 0, 180);
|
||||||
|
|
||||||
|
public static void drawDecorativeBorder(final Graphics2D g2d, final int x, final int y, final int w, final int h, final int strength, final Color color) {
|
||||||
|
final int strength2 = strength * 2;
|
||||||
|
final int strength4 = strength * 4;
|
||||||
|
|
||||||
|
g2d.setColor(color);
|
||||||
|
|
||||||
|
g2d.fillRect(x, y + strength2, strength, h - strength4);
|
||||||
|
g2d.fillRect(x + w - strength, y + strength2, strength, h - strength4);
|
||||||
|
g2d.fillRect(x + strength2, y, w - strength4, strength);
|
||||||
|
g2d.fillRect(x + strength2, y + h - strength, w - strength4, strength);
|
||||||
|
|
||||||
|
g2d.fillRect(x + strength, y + strength, strength2, strength2);
|
||||||
|
g2d.fillRect(x + w - strength - strength2, y + strength, strength2, strength2);
|
||||||
|
g2d.fillRect(x + strength, y + h - strength - strength2, strength2, strength2);
|
||||||
|
g2d.fillRect(x + w - strength - strength2, y + h - strength - strength2, strength2, strength2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void drawDecorativeBorderFilled(final Graphics2D g2d, final int x, final int y, final int w, final int h, final int strength, final Color color) {
|
||||||
|
g2d.setColor(ComponentRenderingSupport.COLOR_WINDOW_BGR);
|
||||||
|
g2d.fillRect(x + strength * 2, y + strength * 2, w - strength * 4, h - strength * 4);
|
||||||
|
|
||||||
|
drawDecorativeBorder(g2d, x, y, w, h, strength, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,208 @@
|
|||||||
|
package de.extio.game_engine.renderer.g2d.control.components;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Component;
|
||||||
|
import java.awt.Graphics;
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.event.ActionListener;
|
||||||
|
import java.awt.event.MouseAdapter;
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.awt.event.MouseMotionListener;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
|
||||||
|
import de.extio.game_engine.spatial2.model.CoordI2;
|
||||||
|
import de.extio.game_engine.spatial2.model.ImmutableCoordI2;
|
||||||
|
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
public abstract class CustomAbstractButton extends Component {
|
||||||
|
|
||||||
|
protected static final int STATE_NORMAL = 0;
|
||||||
|
|
||||||
|
protected static final int STATE_TOGGLED = 1;
|
||||||
|
|
||||||
|
protected static final int STATE_PRESSED = 2;
|
||||||
|
|
||||||
|
protected static final int STATE_HOVERED = 4;
|
||||||
|
|
||||||
|
protected String caption;
|
||||||
|
|
||||||
|
protected int state = STATE_NORMAL;
|
||||||
|
|
||||||
|
protected int fontSize;
|
||||||
|
|
||||||
|
protected final boolean toggle;
|
||||||
|
|
||||||
|
protected double scaleFactor;
|
||||||
|
|
||||||
|
protected boolean dirty = true;
|
||||||
|
|
||||||
|
protected String iconResourceName;
|
||||||
|
|
||||||
|
protected String loadedIconResourceName;
|
||||||
|
|
||||||
|
protected BufferedImage icon;
|
||||||
|
|
||||||
|
protected Color backgroundColor;
|
||||||
|
|
||||||
|
protected CoordI2 lastMousePosition;
|
||||||
|
|
||||||
|
public void setCaption(final String caption) {
|
||||||
|
this.caption = caption;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFontSize(final int fontSize) {
|
||||||
|
this.fontSize = fontSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScaleFactor(final double scaleFactor) {
|
||||||
|
this.scaleFactor = scaleFactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isToggled() {
|
||||||
|
return (this.state & STATE_TOGGLED) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setToggled(final boolean toggled) {
|
||||||
|
if (toggled) {
|
||||||
|
this.state |= STATE_TOGGLED;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.state &= ~STATE_TOGGLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDirty() {
|
||||||
|
return this.dirty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDirty(final boolean dirty) {
|
||||||
|
this.dirty = dirty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIconResourceName(final String iconResourceName) {
|
||||||
|
this.iconResourceName = iconResourceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBackgroundColor(final Color backgroundColor) {
|
||||||
|
this.backgroundColor = backgroundColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CoordI2 getLastMousePosition() {
|
||||||
|
return this.lastMousePosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEnabled(final boolean b) {
|
||||||
|
if (!b) {
|
||||||
|
this.lastMousePosition = null;
|
||||||
|
}
|
||||||
|
super.setEnabled(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CustomAbstractButton(final boolean toggle, final ActionListener listener) {
|
||||||
|
this.setIgnoreRepaint(true);
|
||||||
|
this.setBackground(Color.BLACK);
|
||||||
|
this.toggle = toggle;
|
||||||
|
|
||||||
|
this.addMouseListener(new MouseAdapter() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mousePressed(final MouseEvent e) {
|
||||||
|
if (!CustomAbstractButton.this.isEnabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomAbstractButton.this.state |= STATE_PRESSED;
|
||||||
|
CustomAbstractButton.this.dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseReleased(final MouseEvent e) {
|
||||||
|
if (!CustomAbstractButton.this.isEnabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomAbstractButton.this.state &= ~STATE_PRESSED;
|
||||||
|
if (CustomAbstractButton.this.toggle) {
|
||||||
|
CustomAbstractButton.this.state ^= STATE_TOGGLED;
|
||||||
|
}
|
||||||
|
CustomAbstractButton.this.dirty = true;
|
||||||
|
|
||||||
|
listener.actionPerformed(new ActionEvent(this, CustomAbstractButton.this.state & STATE_TOGGLED, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseEntered(final MouseEvent e) {
|
||||||
|
if (!CustomAbstractButton.this.isEnabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomAbstractButton.this.state |= STATE_HOVERED;
|
||||||
|
CustomAbstractButton.this.dirty = true;
|
||||||
|
CustomAbstractButton.this.lastMousePosition = ImmutableCoordI2.create(e.getX(), e.getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseExited(final MouseEvent e) {
|
||||||
|
CustomAbstractButton.this.lastMousePosition = null;
|
||||||
|
CustomAbstractButton.this.state &= ~STATE_HOVERED;
|
||||||
|
CustomAbstractButton.this.dirty = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.addMouseMotionListener(new MouseMotionListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseMoved(final MouseEvent e) {
|
||||||
|
CustomAbstractButton.this.lastMousePosition = ImmutableCoordI2.create(e.getX(), e.getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseDragged(final MouseEvent e) {
|
||||||
|
CustomAbstractButton.this.lastMousePosition = ImmutableCoordI2.create(e.getX(), e.getY());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(final Graphics g) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() {
|
||||||
|
this.flushIcon();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void loadIcon() {
|
||||||
|
if (this.iconResourceName != null) {
|
||||||
|
this.loadedIconResourceName = this.iconResourceName;
|
||||||
|
this.flushIcon();
|
||||||
|
|
||||||
|
try (InputStream stream = new FileInputStream(this.iconResourceName)) {
|
||||||
|
this.icon = ImageIO.read(stream);
|
||||||
|
}
|
||||||
|
catch (final IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.icon == null) {
|
||||||
|
throw new RuntimeException("Resource not found: " + this.iconResourceName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void flushIcon() {
|
||||||
|
if (this.icon != null) {
|
||||||
|
this.icon.flush();
|
||||||
|
this.icon = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
package de.extio.game_engine.renderer.g2d.control.components;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Graphics;
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.event.ActionListener;
|
||||||
|
|
||||||
|
import de.extio.game_engine.renderer.g2d.bo.rendering.G2DDrawFont;
|
||||||
|
import de.extio.game_engine.spatial2.model.CoordI2;
|
||||||
|
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
public class CustomButton extends CustomAbstractButton {
|
||||||
|
|
||||||
|
public CustomButton(final boolean toggle, final ActionListener listener) {
|
||||||
|
super(toggle, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void paint(final Graphics g) {
|
||||||
|
final Graphics2D g2d = (Graphics2D) g;
|
||||||
|
|
||||||
|
float h, s, b;
|
||||||
|
|
||||||
|
final int borderStrength = (this.getWidth() < 48 || this.getHeight() < 48) ? 1 : 2;
|
||||||
|
final int bgrStrength = (this.getWidth() < 48 || this.getHeight() < 48) ? 0 : 2;
|
||||||
|
|
||||||
|
if (this.backgroundColor == null) {
|
||||||
|
if ((this.state & STATE_TOGGLED) == 0) {
|
||||||
|
h = ComponentRenderingSupport.HSB_COMPONENT_BGR.h();
|
||||||
|
s = ComponentRenderingSupport.HSB_COMPONENT_BGR.s();
|
||||||
|
b = ComponentRenderingSupport.HSB_COMPONENT_BGR.b();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
h = ComponentRenderingSupport.HSB_COMPONENT_SELECTED_0.b();
|
||||||
|
s = ComponentRenderingSupport.HSB_COMPONENT_SELECTED_0.s();
|
||||||
|
b = ComponentRenderingSupport.HSB_COMPONENT_SELECTED_0.b();
|
||||||
|
}
|
||||||
|
if ((this.state & STATE_PRESSED) != 0) {
|
||||||
|
b += 0.40F;
|
||||||
|
}
|
||||||
|
else if ((this.state & STATE_HOVERED) != 0) {
|
||||||
|
b += 0.25F;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
final float[] hsb = new float[3];
|
||||||
|
Color.RGBtoHSB(this.backgroundColor.getRed(), this.backgroundColor.getGreen(), this.backgroundColor.getBlue(), hsb);
|
||||||
|
h = hsb[0];
|
||||||
|
s = hsb[1];
|
||||||
|
|
||||||
|
if ((this.state & STATE_TOGGLED) == 0) {
|
||||||
|
b = 0.30F;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
b = 0.50F;
|
||||||
|
}
|
||||||
|
if ((this.state & STATE_PRESSED) != 0) {
|
||||||
|
b += 0.40F;
|
||||||
|
}
|
||||||
|
else if ((this.state & STATE_HOVERED) != 0) {
|
||||||
|
b += 0.25F;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g2d.setColor(Color.getHSBColor(h, s, b));
|
||||||
|
g2d.fillRect(2 + bgrStrength, 2 + bgrStrength, this.getWidth() - 5 - bgrStrength * 2, this.getHeight() - 5 - bgrStrength * 2);
|
||||||
|
|
||||||
|
if (this.iconResourceName != null && (this.icon == null || !this.iconResourceName.equals(this.loadedIconResourceName))) {
|
||||||
|
this.loadIcon();
|
||||||
|
}
|
||||||
|
if (this.icon != null) {
|
||||||
|
g2d.drawImage(this.icon, 4 + bgrStrength, 4 + bgrStrength, this.getWidth() - 4 - bgrStrength, this.getHeight() - 4 - bgrStrength, 0, 0, this.icon.getWidth(), this.icon.getHeight(), null);
|
||||||
|
}
|
||||||
|
else if (this.caption != null) {
|
||||||
|
if ((this.state & (STATE_HOVERED | STATE_TOGGLED)) != 0) {
|
||||||
|
g2d.setColor(Color.WHITE);
|
||||||
|
}
|
||||||
|
else if (this.isEnabled()) {
|
||||||
|
g2d.setColor(Color.LIGHT_GRAY);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
g2d.setColor(Color.GRAY);
|
||||||
|
}
|
||||||
|
final CoordI2 textDim = G2DDrawFont.getTextDimensions(this.caption, g2d, this.fontSize, this.scaleFactor);
|
||||||
|
G2DDrawFont.renderText(g2d,
|
||||||
|
textDim,
|
||||||
|
this.scaleFactor,
|
||||||
|
(int) ((this.getWidth() - (textDim.getX())) / 2 / this.scaleFactor),
|
||||||
|
(int) ((this.getHeight() - (textDim.getY())) / 2 / this.scaleFactor),
|
||||||
|
this.fontSize,
|
||||||
|
this.caption);
|
||||||
|
}
|
||||||
|
|
||||||
|
Color color = (this.state & STATE_HOVERED) != 0 ? ComponentRenderingSupport.COLOR_COMPONENT_SELECTED_0 : ComponentRenderingSupport.COLOR_COMPONENT_BORDER0;
|
||||||
|
ComponentRenderingSupport.drawDecorativeBorder(g2d, 0, 0, this.getWidth() - 1, this.getHeight() - 1, borderStrength, color);
|
||||||
|
color = this.isEnabled() ? (this.state & STATE_HOVERED) != 0 ? ComponentRenderingSupport.COLOR_COMPONENT_SELECTED_1 : ComponentRenderingSupport.COLOR_COMPONENT_BORDER1 : ComponentRenderingSupport.COLOR_COMPONENT_BORDER1_DIS;
|
||||||
|
ComponentRenderingSupport.drawDecorativeBorder(g2d, borderStrength, borderStrength, this.getWidth() - 1 - 2 * borderStrength, this.getHeight() - 1 - 2 * borderStrength, borderStrength, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package de.extio.game_engine.renderer.g2d.control.components;
|
||||||
|
|
||||||
|
import java.awt.Component;
|
||||||
|
import java.awt.Graphics;
|
||||||
|
|
||||||
|
import javax.swing.JScrollPane;
|
||||||
|
import javax.swing.RepaintManager;
|
||||||
|
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
public class CustomJScrollPane extends JScrollPane {
|
||||||
|
|
||||||
|
private Graphics drawGraphics;
|
||||||
|
|
||||||
|
public CustomJScrollPane(final Component view) {
|
||||||
|
super(view);
|
||||||
|
|
||||||
|
this.setIgnoreRepaint(true);
|
||||||
|
this.putClientProperty("AATextInfoPropertyKey", null);
|
||||||
|
this.setDoubleBuffered(false);
|
||||||
|
RepaintManager.currentManager(this).setDoubleBufferingEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void paintAll(final Graphics g) {
|
||||||
|
if (this.drawGraphics == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
super.paintAll(this.drawGraphics);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void paint(final Graphics g) {
|
||||||
|
if (this.drawGraphics == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
super.paint(this.drawGraphics);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(final Graphics g) {
|
||||||
|
if (this.drawGraphics == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
super.update(this.drawGraphics);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Graphics getDrawGraphics() {
|
||||||
|
return this.drawGraphics;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDrawGraphics(final Graphics drawGraphics) {
|
||||||
|
this.drawGraphics = drawGraphics;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package de.extio.game_engine.renderer.g2d.control.components;
|
||||||
|
|
||||||
|
import java.awt.Graphics;
|
||||||
|
|
||||||
|
import javax.swing.JTextArea;
|
||||||
|
import javax.swing.RepaintManager;
|
||||||
|
|
||||||
|
import de.extio.game_engine.renderer.g2d.control.G2DControlHasExclusiveKeyEvent;
|
||||||
|
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
public class CustomJTextArea extends JTextArea implements G2DControlHasExclusiveKeyEvent {
|
||||||
|
|
||||||
|
// private final static Color CARET_COLOR = new ImmutableRgbaColor(RgbaColor.YELLOW_SPACECRAFT.getR(), RgbaColor.YELLOW_SPACECRAFT.getG(), RgbaColor.YELLOW_SPACECRAFT.getB(), 190).toAwtColor();
|
||||||
|
//
|
||||||
|
// private final static Color CARET_COLOR2 = new ImmutableRgbaColor(RgbaColor.YELLOW_SPACECRAFT.getR() / 2, RgbaColor.YELLOW_SPACECRAFT.getG() / 2, RgbaColor.YELLOW_SPACECRAFT.getB() / 2, 100).toAwtColor();
|
||||||
|
|
||||||
|
protected boolean dirty = true;
|
||||||
|
|
||||||
|
private Graphics drawGraphics;
|
||||||
|
|
||||||
|
public CustomJTextArea() {
|
||||||
|
this.setIgnoreRepaint(true);
|
||||||
|
this.putClientProperty("AATextInfoPropertyKey", null);
|
||||||
|
this.setDoubleBuffered(false);
|
||||||
|
RepaintManager.currentManager(this).setDoubleBufferingEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void paintAll(final Graphics g) {
|
||||||
|
if (this.drawGraphics == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
super.paintAll(this.drawGraphics);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void paint(final Graphics g) {
|
||||||
|
if (this.drawGraphics == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
super.paint(this.drawGraphics);
|
||||||
|
|
||||||
|
// try {
|
||||||
|
// Rectangle2D rect = this.getUI().modelToView2D(this, this.getCaret().getDot(), Bias.Forward);
|
||||||
|
// this.drawGraphics.setColor((System.currentTimeMillis() % 1000 < 500) ? CARET_COLOR : CARET_COLOR2);
|
||||||
|
// this.drawGraphics.fillRect((int)rect.getX(), (int)rect.getY(), Math.max(2, (int)rect.getWidth()), Math.max(6, (int)rect.getHeight()));
|
||||||
|
// }
|
||||||
|
// catch (BadLocationException e) {
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(final Graphics g) {
|
||||||
|
this.dirty = true;
|
||||||
|
|
||||||
|
if (this.drawGraphics == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
super.update(this.drawGraphics);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDirty() {
|
||||||
|
return this.dirty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDirty(final boolean dirty) {
|
||||||
|
this.dirty = dirty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Graphics getDrawGraphics() {
|
||||||
|
return this.drawGraphics;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDrawGraphics(final Graphics drawGraphics) {
|
||||||
|
this.drawGraphics = drawGraphics;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
package de.extio.game_engine.renderer.g2d.control.components;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Graphics;
|
||||||
|
import java.awt.geom.Rectangle2D;
|
||||||
|
|
||||||
|
import javax.swing.JTextField;
|
||||||
|
import javax.swing.RepaintManager;
|
||||||
|
import javax.swing.text.BadLocationException;
|
||||||
|
import javax.swing.text.Position.Bias;
|
||||||
|
|
||||||
|
import de.extio.game_engine.renderer.g2d.control.G2DControlHasExclusiveKeyEvent;
|
||||||
|
import de.extio.game_engine.renderer.model.ImmutableRgbaColor;
|
||||||
|
import de.extio.game_engine.renderer.model.RgbaColor;
|
||||||
|
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
public class CustomJTextField extends JTextField implements G2DControlHasExclusiveKeyEvent {
|
||||||
|
|
||||||
|
// private final static Logger LOGGER = LogManager.getLogger();
|
||||||
|
|
||||||
|
private final static Color CARET_COLOR = new ImmutableRgbaColor(RgbaColor.YELLOW.getR(), RgbaColor.YELLOW.getG(), RgbaColor.YELLOW.getB(), 190).toAwtColor();
|
||||||
|
|
||||||
|
private final static Color CARET_COLOR2 = new ImmutableRgbaColor(RgbaColor.YELLOW.getR() / 2, RgbaColor.YELLOW.getG() / 2, RgbaColor.YELLOW.getB() / 2, 100).toAwtColor();
|
||||||
|
|
||||||
|
protected boolean dirty = true;
|
||||||
|
|
||||||
|
private Graphics drawGraphics;
|
||||||
|
|
||||||
|
public CustomJTextField() {
|
||||||
|
this.setIgnoreRepaint(true);
|
||||||
|
this.putClientProperty("AATextInfoPropertyKey", null);
|
||||||
|
this.setDoubleBuffered(false);
|
||||||
|
RepaintManager.currentManager(this).setDoubleBufferingEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void paintAll(final Graphics g) {
|
||||||
|
if (this.drawGraphics == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
super.paintAll(this.drawGraphics);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void paint(final Graphics g) {
|
||||||
|
if (this.drawGraphics == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
super.paint(this.drawGraphics);
|
||||||
|
|
||||||
|
if (this.isFocusOwner()) {
|
||||||
|
try {
|
||||||
|
final Rectangle2D rect = this.getUI().modelToView2D(this, this.getCaret().getDot(), Bias.Forward);
|
||||||
|
if (rect != null) {
|
||||||
|
this.drawGraphics.setColor((System.currentTimeMillis() % 1000 < 500) ? CARET_COLOR : CARET_COLOR2);
|
||||||
|
this.drawGraphics.fillRect((int) rect.getX(), (int) rect.getY(), Math.max(2, (int) rect.getWidth()), Math.max(6, (int) rect.getHeight()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (final BadLocationException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(final Graphics g) {
|
||||||
|
this.dirty = true;
|
||||||
|
|
||||||
|
if (this.drawGraphics == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
super.update(this.drawGraphics);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDirty() {
|
||||||
|
return this.dirty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDirty(final boolean dirty) {
|
||||||
|
this.dirty = dirty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Graphics getDrawGraphics() {
|
||||||
|
return this.drawGraphics;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDrawGraphics(final Graphics drawGraphics) {
|
||||||
|
this.drawGraphics = drawGraphics;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,226 @@
|
|||||||
|
package de.extio.game_engine.renderer.g2d.control.components;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Component;
|
||||||
|
import java.awt.Graphics;
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.event.ActionListener;
|
||||||
|
import java.awt.event.MouseAdapter;
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.awt.event.MouseMotionListener;
|
||||||
|
|
||||||
|
import de.extio.game_engine.renderer.g2d.bo.rendering.G2DDrawFont;
|
||||||
|
import de.extio.game_engine.renderer.model.DrawFontRenderingBoTextAlignment;
|
||||||
|
import de.extio.game_engine.spatial2.model.CoordI2;
|
||||||
|
import de.extio.game_engine.spatial2.model.ImmutableCoordI2;
|
||||||
|
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
public class CustomLabel extends Component {
|
||||||
|
|
||||||
|
protected static final int STATE_NORMAL = 0;
|
||||||
|
|
||||||
|
protected static final int STATE_PRESSED = 2;
|
||||||
|
|
||||||
|
protected static final int STATE_HOVERED = 4;
|
||||||
|
|
||||||
|
protected String caption;
|
||||||
|
|
||||||
|
protected int state = STATE_NORMAL;
|
||||||
|
|
||||||
|
protected int fontSize;
|
||||||
|
|
||||||
|
protected double scaleFactor;
|
||||||
|
|
||||||
|
protected boolean enabled;
|
||||||
|
|
||||||
|
protected Color backgroundColor;
|
||||||
|
|
||||||
|
protected Color foregroundColor;
|
||||||
|
|
||||||
|
private DrawFontRenderingBoTextAlignment textAlignment;
|
||||||
|
|
||||||
|
protected boolean dirty = true;
|
||||||
|
|
||||||
|
private CoordI2 lastMousePosition;
|
||||||
|
|
||||||
|
public void setCaption(final String caption) {
|
||||||
|
this.caption = caption;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFontSize(final int fontSize) {
|
||||||
|
this.fontSize = fontSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScaleFactor(final double scaleFactor) {
|
||||||
|
this.scaleFactor = scaleFactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEnabled(final boolean enabled) {
|
||||||
|
if (!enabled) {
|
||||||
|
this.lastMousePosition = null;
|
||||||
|
}
|
||||||
|
this.enabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBackgroundColor(final Color color) {
|
||||||
|
this.backgroundColor = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setForegroundColor(final Color color) {
|
||||||
|
this.foregroundColor = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DrawFontRenderingBoTextAlignment getTextAlignment() {
|
||||||
|
return this.textAlignment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTextAlignment(final DrawFontRenderingBoTextAlignment textAlignment) {
|
||||||
|
this.textAlignment = textAlignment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDirty() {
|
||||||
|
return this.dirty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDirty(final boolean dirty) {
|
||||||
|
this.dirty = dirty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CoordI2 getLastMousePosition() {
|
||||||
|
return this.lastMousePosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CustomLabel(final ActionListener listener) {
|
||||||
|
this.setIgnoreRepaint(true);
|
||||||
|
|
||||||
|
this.addMouseListener(new MouseAdapter() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mousePressed(final MouseEvent e) {
|
||||||
|
CustomLabel.this.state |= STATE_PRESSED;
|
||||||
|
CustomLabel.this.dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseReleased(final MouseEvent e) {
|
||||||
|
CustomLabel.this.state &= ~STATE_PRESSED;
|
||||||
|
listener.actionPerformed(new ActionEvent(this, 0, null));
|
||||||
|
CustomLabel.this.dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseEntered(final MouseEvent e) {
|
||||||
|
CustomLabel.this.state |= STATE_HOVERED;
|
||||||
|
CustomLabel.this.dirty = true;
|
||||||
|
CustomLabel.this.lastMousePosition = ImmutableCoordI2.create(e.getX(), e.getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseExited(final MouseEvent e) {
|
||||||
|
CustomLabel.this.state &= ~STATE_HOVERED;
|
||||||
|
CustomLabel.this.dirty = true;
|
||||||
|
CustomLabel.this.lastMousePosition = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.addMouseMotionListener(new MouseMotionListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseMoved(final MouseEvent e) {
|
||||||
|
CustomLabel.this.lastMousePosition = ImmutableCoordI2.create(e.getX(), e.getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseDragged(final MouseEvent e) {
|
||||||
|
CustomLabel.this.lastMousePosition = ImmutableCoordI2.create(e.getX(), e.getY());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(final Graphics g) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void paint(final Graphics g) {
|
||||||
|
final Graphics2D g2d = (Graphics2D) g;
|
||||||
|
|
||||||
|
if (this.backgroundColor != null) {
|
||||||
|
g2d.setColor(this.backgroundColor);
|
||||||
|
g2d.fillRect(0, 0, this.getWidth(), this.getHeight());
|
||||||
|
}
|
||||||
|
|
||||||
|
float h, s, b;
|
||||||
|
if (this.foregroundColor == null) {
|
||||||
|
h = 0.0F;
|
||||||
|
s = 0.0F;
|
||||||
|
b = 0.80F;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
final float[] hsb = Color.RGBtoHSB(this.foregroundColor.getRed(), this.foregroundColor.getGreen(), this.foregroundColor.getBlue(), null);
|
||||||
|
h = hsb[0];
|
||||||
|
s = hsb[1];
|
||||||
|
b = hsb[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.enabled) {
|
||||||
|
b -= 0.30F;
|
||||||
|
}
|
||||||
|
else if ((this.state & STATE_PRESSED) != 0) {
|
||||||
|
h = ComponentRenderingSupport.HSB_COMPONENT_SELECTED_0.b();
|
||||||
|
s = ComponentRenderingSupport.HSB_COMPONENT_SELECTED_0.s();
|
||||||
|
b = ComponentRenderingSupport.HSB_COMPONENT_SELECTED_0.b() + 0.4f;
|
||||||
|
}
|
||||||
|
else if ((this.state & STATE_HOVERED) != 0) {
|
||||||
|
b += 0.15F;
|
||||||
|
}
|
||||||
|
|
||||||
|
h = Math.max(0.0f, Math.min(1.0f, h));
|
||||||
|
s = Math.max(0.0f, Math.min(1.0f, s));
|
||||||
|
b = Math.max(0.0f, Math.min(1.0f, b));
|
||||||
|
g2d.setColor(Color.getHSBColor(h, s, b));
|
||||||
|
|
||||||
|
final DrawFontRenderingBoTextAlignment alignment = this.textAlignment == null ? DrawFontRenderingBoTextAlignment.CENTER : this.textAlignment;
|
||||||
|
final CoordI2 textDim = G2DDrawFont.getTextDimensions(this.caption, g2d, this.fontSize, this.scaleFactor);
|
||||||
|
switch (alignment) {
|
||||||
|
case LEFT: {
|
||||||
|
G2DDrawFont.renderText(g2d,
|
||||||
|
this.scaleFactor,
|
||||||
|
1,
|
||||||
|
(int) ((this.getHeight() - (textDim.getY())) / 2 / this.scaleFactor),
|
||||||
|
this.fontSize,
|
||||||
|
this.caption);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case CENTER: {
|
||||||
|
G2DDrawFont.renderText(g2d,
|
||||||
|
textDim,
|
||||||
|
this.scaleFactor,
|
||||||
|
(int) ((this.getWidth() - (textDim.getX())) / 2 / this.scaleFactor),
|
||||||
|
(int) ((this.getHeight() - (textDim.getY())) / 2 / this.scaleFactor),
|
||||||
|
this.fontSize,
|
||||||
|
this.caption);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case RIGHT: {
|
||||||
|
G2DDrawFont.renderText(g2d,
|
||||||
|
textDim,
|
||||||
|
this.scaleFactor,
|
||||||
|
(int) ((this.getWidth() - textDim.getX()) / this.scaleFactor),
|
||||||
|
(int) ((this.getHeight() - (textDim.getY())) / 2 / this.scaleFactor),
|
||||||
|
this.fontSize,
|
||||||
|
this.caption);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,279 @@
|
|||||||
|
package de.extio.game_engine.renderer.g2d.control.components;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Component;
|
||||||
|
import java.awt.Graphics;
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.event.ActionListener;
|
||||||
|
import java.awt.event.MouseAdapter;
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.awt.event.MouseMotionListener;
|
||||||
|
|
||||||
|
import de.extio.game_engine.spatial2.model.CoordI2;
|
||||||
|
import de.extio.game_engine.spatial2.model.ImmutableCoordI2;
|
||||||
|
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
public class CustomSlider extends Component {
|
||||||
|
|
||||||
|
protected static final int STATE_NORMAL = 0;
|
||||||
|
|
||||||
|
protected static final int STATE_HOVERED = 4;
|
||||||
|
|
||||||
|
protected int state = STATE_NORMAL;
|
||||||
|
|
||||||
|
protected double scaleFactor;
|
||||||
|
|
||||||
|
protected boolean enabled;
|
||||||
|
|
||||||
|
protected Color color;
|
||||||
|
|
||||||
|
protected boolean horizontal;
|
||||||
|
|
||||||
|
protected double value;
|
||||||
|
|
||||||
|
private double value2;
|
||||||
|
|
||||||
|
protected boolean dirty = true;
|
||||||
|
|
||||||
|
private CoordI2 lastMousePosition;
|
||||||
|
|
||||||
|
private final ActionListener actionListener;
|
||||||
|
|
||||||
|
private ActionEvent lastActionEvent;
|
||||||
|
|
||||||
|
private long lastActionEventReleased;
|
||||||
|
|
||||||
|
public void setScaleFactor(final double scaleFactor) {
|
||||||
|
this.scaleFactor = scaleFactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEnabled(final boolean enabled) {
|
||||||
|
if (!enabled) {
|
||||||
|
this.lastMousePosition = null;
|
||||||
|
}
|
||||||
|
this.enabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setColor(final Color color) {
|
||||||
|
this.color = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDirty() {
|
||||||
|
return this.dirty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDirty(final boolean dirty) {
|
||||||
|
this.dirty = dirty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHorizontal(final boolean horizontal) {
|
||||||
|
this.horizontal = horizontal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValue(final double value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getValue() {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getValue2() {
|
||||||
|
return this.value2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValue2(final double value2) {
|
||||||
|
this.value2 = value2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CoordI2 getLastMousePosition() {
|
||||||
|
return this.lastMousePosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CustomSlider(final ActionListener listener) {
|
||||||
|
this.setIgnoreRepaint(true);
|
||||||
|
this.actionListener = listener;
|
||||||
|
|
||||||
|
this.addMouseListener(new MouseAdapter() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mousePressed(final MouseEvent e) {
|
||||||
|
CustomSlider.this.dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseReleased(final MouseEvent e) {
|
||||||
|
CustomSlider.this.dirty = true;
|
||||||
|
CustomSlider.this.action(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseEntered(final MouseEvent e) {
|
||||||
|
CustomSlider.this.state |= STATE_HOVERED;
|
||||||
|
CustomSlider.this.dirty = true;
|
||||||
|
CustomSlider.this.lastMousePosition = ImmutableCoordI2.create(e.getX(), e.getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseExited(final MouseEvent e) {
|
||||||
|
CustomSlider.this.state &= ~STATE_HOVERED;
|
||||||
|
CustomSlider.this.dirty = true;
|
||||||
|
CustomSlider.this.lastMousePosition = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
this.addMouseMotionListener(new MouseMotionListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseMoved(final MouseEvent e) {
|
||||||
|
CustomSlider.this.lastMousePosition = ImmutableCoordI2.create(e.getX(), e.getY());
|
||||||
|
CustomSlider.this.dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseDragged(final MouseEvent e) {
|
||||||
|
CustomSlider.this.lastMousePosition = ImmutableCoordI2.create(e.getX(), e.getY());
|
||||||
|
CustomSlider.this.dirty = true;
|
||||||
|
CustomSlider.this.action(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void action(final MouseEvent e) {
|
||||||
|
double value = CustomSlider.this.horizontal ? ((double) e.getX() / (double) CustomSlider.this.getWidth()) : (1.0 - ((double) e.getY() / (double) CustomSlider.this.getHeight()));
|
||||||
|
value = Math.min(1.0, Math.max(0.0, value));
|
||||||
|
CustomSlider.this.setValue(value);
|
||||||
|
synchronized (this) {
|
||||||
|
this.lastActionEvent = new ActionEvent(this, 0, Double.toString(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void releaseEvents() {
|
||||||
|
synchronized (this) {
|
||||||
|
if (this.lastActionEvent != null && Math.abs(System.currentTimeMillis() - this.lastActionEventReleased) >= 200) {
|
||||||
|
this.lastActionEventReleased = System.currentTimeMillis();
|
||||||
|
this.actionListener.actionPerformed(this.lastActionEvent);
|
||||||
|
this.lastActionEvent = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(final Graphics g) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void paint(final Graphics g) {
|
||||||
|
final Graphics2D g2d = (Graphics2D) g;
|
||||||
|
|
||||||
|
float h, s, b;
|
||||||
|
if (this.color == null) {
|
||||||
|
h = 0.0F;
|
||||||
|
s = 0.0F;
|
||||||
|
b = 0.80F;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
final float[] hsb = Color.RGBtoHSB(this.color.getRed(), this.color.getGreen(), this.color.getBlue(), null);
|
||||||
|
h = hsb[0];
|
||||||
|
s = hsb[1];
|
||||||
|
b = hsb[2];
|
||||||
|
}
|
||||||
|
if (!this.enabled) {
|
||||||
|
b -= 0.30F;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main line
|
||||||
|
if (this.horizontal) {
|
||||||
|
this.drawLine(g2d, h, s, b, 0, this.getHeight() / 2, this.getWidth(), this.getHeight() / 2, this.horizontal);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.drawLine(g2d, h, s, b, this.getWidth() / 2, 0, this.getWidth() / 2, this.getHeight(), this.horizontal);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Small lines
|
||||||
|
b -= 0.20F;
|
||||||
|
final int length = this.horizontal ? this.getWidth() : this.getHeight();
|
||||||
|
for (int i = 1; i < 10; i++) {
|
||||||
|
final int pos = (int) ((double) length / 10.0 * i);
|
||||||
|
if (this.horizontal) {
|
||||||
|
this.drawLine(g2d, h, s, b, pos, this.getHeight() / 4, pos, this.getHeight() / 4 * 3, !this.horizontal);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.drawLine(g2d, h, s, b, this.getWidth() / 4, pos, this.getWidth() / 4 * 3, pos, !this.horizontal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b += 0.20F;
|
||||||
|
|
||||||
|
// Pointer
|
||||||
|
if ((this.state & STATE_HOVERED) != 0 && this.lastMousePosition != null) {
|
||||||
|
try {
|
||||||
|
if (this.horizontal) {
|
||||||
|
this.drawLine(g2d, h, s, b, this.lastMousePosition.getX(), 0, this.lastMousePosition.getX(), this.getHeight(), !this.horizontal);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.drawLine(g2d, h, s, b, 0, this.lastMousePosition.getY(), this.getWidth(), this.lastMousePosition.getY(), !this.horizontal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (final NullPointerException exc) {
|
||||||
|
// AWT MouseAdapter can set this.lastMousePosition null at any time
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value2
|
||||||
|
if (this.value2 != this.value) {
|
||||||
|
if (this.horizontal) {
|
||||||
|
this.drawLine(g2d, h, s, b, (int) (this.getWidth() * this.value2), 0, (int) (this.getWidth() * this.value2), this.getHeight(), !this.horizontal);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.drawLine(g2d, h, s, b, 0, (int) (this.getHeight() * (1.0 - this.value2)), this.getWidth(), (int) (this.getHeight() * (1.0 - this.value2)), !this.horizontal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value
|
||||||
|
b += 0.15F;
|
||||||
|
if (this.horizontal) {
|
||||||
|
this.drawLine(g2d, h, s, b, (int) (this.getWidth() * this.value), 0, (int) (this.getWidth() * this.value), this.getHeight(), !this.horizontal);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.drawLine(g2d, h, s, b, 0, (int) (this.getHeight() * (1.0 - this.value)), this.getWidth(), (int) (this.getHeight() * (1.0 - this.value)), !this.horizontal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyColor(final Graphics2D g2d, float h, float s, float b) {
|
||||||
|
h = Math.max(0.0f, Math.min(1.0f, h));
|
||||||
|
s = Math.max(0.0f, Math.min(1.0f, s));
|
||||||
|
b = Math.max(0.0f, Math.min(1.0f, b));
|
||||||
|
g2d.setColor(Color.getHSBColor(h, s, b));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawLine(final Graphics2D g2d, final float h, float s, float b, int x, int y, int x2, int y2, final boolean horizontal) {
|
||||||
|
x = Math.max(1, x);
|
||||||
|
x = Math.min(this.getWidth() - 2, x);
|
||||||
|
y = Math.max(1, y);
|
||||||
|
y = Math.min(this.getHeight() - 2, y);
|
||||||
|
x2 = Math.max(1, x2);
|
||||||
|
x2 = Math.min(this.getWidth() - 2, x2);
|
||||||
|
y2 = Math.max(1, y2);
|
||||||
|
y2 = Math.min(this.getHeight() - 2, y2);
|
||||||
|
|
||||||
|
this.applyColor(g2d, h, s, b);
|
||||||
|
g2d.drawLine(x, y, x2, y2);
|
||||||
|
|
||||||
|
s -= 0.2F;
|
||||||
|
b -= 0.2F;
|
||||||
|
this.applyColor(g2d, h, s, b);
|
||||||
|
if (horizontal) {
|
||||||
|
g2d.drawLine(x, y - 1, x2, y2 - 1);
|
||||||
|
g2d.drawLine(x, y + 1, x2, y2 + 1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
g2d.drawLine(x - 1, y, x2 - 1, y2);
|
||||||
|
g2d.drawLine(x + 1, y, x2 + 1, y2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
package de.extio.game_engine.renderer.g2d.control.components;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Graphics;
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.event.ActionListener;
|
||||||
|
|
||||||
|
import de.extio.game_engine.renderer.g2d.bo.rendering.G2DDrawFont;
|
||||||
|
import de.extio.game_engine.spatial2.model.CoordI2;
|
||||||
|
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
public class CustomSwitch extends CustomAbstractButton {
|
||||||
|
|
||||||
|
private boolean drawBorder;
|
||||||
|
|
||||||
|
public CustomSwitch(final boolean toggle, final ActionListener listener) {
|
||||||
|
super(toggle, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void paint(final Graphics g) {
|
||||||
|
final Graphics2D g2d = (Graphics2D) g;
|
||||||
|
|
||||||
|
final int dimY = this.getHeight() - 1;
|
||||||
|
final int dimX = (int) (dimY * 1.5);
|
||||||
|
|
||||||
|
float h, s, b;
|
||||||
|
if ((this.state & STATE_TOGGLED) == 0) {
|
||||||
|
h = ComponentRenderingSupport.HSB_COMPONENT_BGR.h();
|
||||||
|
s = ComponentRenderingSupport.HSB_COMPONENT_BGR.s();
|
||||||
|
b = ComponentRenderingSupport.HSB_COMPONENT_BGR.b();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
h = ComponentRenderingSupport.HSB_COMPONENT_SELECTED_0.b();
|
||||||
|
s = ComponentRenderingSupport.HSB_COMPONENT_SELECTED_0.s();
|
||||||
|
b = ComponentRenderingSupport.HSB_COMPONENT_SELECTED_0.b();
|
||||||
|
}
|
||||||
|
if ((this.state & STATE_PRESSED) != 0) {
|
||||||
|
b += 0.40F;
|
||||||
|
}
|
||||||
|
else if ((this.state & STATE_HOVERED) != 0) {
|
||||||
|
b += 0.25F;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Color bodyColor = Color.getHSBColor(h, s, b);
|
||||||
|
Color border0Color;
|
||||||
|
Color border1Color;
|
||||||
|
if (dimX < 45) {
|
||||||
|
border0Color = this.isEnabled() ? ComponentRenderingSupport.COLOR_COMPONENT_BORDER1 : ComponentRenderingSupport.COLOR_COMPONENT_BORDER1_DIS;
|
||||||
|
border1Color = bodyColor;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
border0Color = ComponentRenderingSupport.COLOR_COMPONENT_BORDER0;
|
||||||
|
border1Color = this.isEnabled() ? ComponentRenderingSupport.COLOR_COMPONENT_BORDER1 : ComponentRenderingSupport.COLOR_COMPONENT_BORDER1_DIS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((this.state & STATE_TOGGLED) == 0) {
|
||||||
|
g2d.setColor(bodyColor);
|
||||||
|
g2d.fillRect(8, 8, (int) (dimX / 1.5) - 12, this.getHeight() - 15);
|
||||||
|
|
||||||
|
ComponentRenderingSupport.drawDecorativeBorder(g2d, 6, 4, (int) (dimX / 1.5) - 6, this.getHeight() - 9, 2, border0Color);
|
||||||
|
ComponentRenderingSupport.drawDecorativeBorder(g2d, 8, 6, (int) (dimX / 1.5) - 10, this.getHeight() - 13, 2, border1Color);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
g2d.setColor(bodyColor);
|
||||||
|
g2d.fillRect(dimX - (int) (dimX / 1.5), 8, (int) (dimX / 1.5) - 12, this.getHeight() - 15);
|
||||||
|
|
||||||
|
ComponentRenderingSupport.drawDecorativeBorder(g2d, dimX - (int) (dimX / 1.5) - 3, 4, (int) (dimX / 1.5) - 6, this.getHeight() - 9, 2, border0Color);
|
||||||
|
ComponentRenderingSupport.drawDecorativeBorder(g2d, dimX - (int) (dimX / 1.5) - 1, 6, (int) (dimX / 1.5) - 10, this.getHeight() - 13, 2, border1Color);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((this.state & STATE_HOVERED) != 0) {
|
||||||
|
g2d.setColor(Color.WHITE);
|
||||||
|
}
|
||||||
|
else if (this.isEnabled()) {
|
||||||
|
g2d.setColor(Color.LIGHT_GRAY);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
g2d.setColor(Color.GRAY);
|
||||||
|
}
|
||||||
|
final CoordI2 textDim = G2DDrawFont.getTextDimensions(this.caption, g2d, this.fontSize, this.scaleFactor);
|
||||||
|
G2DDrawFont.renderText(g2d,
|
||||||
|
textDim,
|
||||||
|
this.scaleFactor,
|
||||||
|
(int) (dimX / this.scaleFactor) + 3,
|
||||||
|
(int) ((this.getHeight() - (textDim.getY())) / 2 / this.scaleFactor),
|
||||||
|
this.fontSize,
|
||||||
|
this.caption);
|
||||||
|
|
||||||
|
if (this.drawBorder) {
|
||||||
|
ComponentRenderingSupport.drawDecorativeBorder(g2d, 0, 0, this.getWidth() - 1, this.getHeight() - 1, 2, (this.state & STATE_HOVERED) != 0 ? ComponentRenderingSupport.COLOR_COMPONENT_SELECTED_0 : ComponentRenderingSupport.COLOR_COMPONENT_BORDER0);
|
||||||
|
ComponentRenderingSupport.drawDecorativeBorder(g2d, 2, 2, this.getWidth() - 5, this.getHeight() - 5, 2, this.isEnabled() ? (this.state & STATE_HOVERED) != 0 ? ComponentRenderingSupport.COLOR_COMPONENT_SELECTED_1 : ComponentRenderingSupport.COLOR_COMPONENT_BORDER1 : ComponentRenderingSupport.COLOR_COMPONENT_BORDER1_DIS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDrawBorder() {
|
||||||
|
return this.drawBorder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDrawBorder(final boolean drawBorder) {
|
||||||
|
this.drawBorder = drawBorder;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
package de.extio.game_engine.renderer.g2d.control.components;
|
||||||
|
|
||||||
|
import java.awt.Graphics;
|
||||||
|
|
||||||
|
import javax.swing.JTable;
|
||||||
|
import javax.swing.table.DefaultTableModel;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class CustomTable extends JTable {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(CustomTable.class);
|
||||||
|
|
||||||
|
private static final long serialVersionUID = -9157983277960164169L;
|
||||||
|
|
||||||
|
protected boolean dirty = true;
|
||||||
|
|
||||||
|
private Graphics drawGraphics;
|
||||||
|
|
||||||
|
public CustomTable() {
|
||||||
|
super(new CustomTableModel());
|
||||||
|
|
||||||
|
this.setIgnoreRepaint(true);
|
||||||
|
this.setDoubleBuffered(false);
|
||||||
|
this.putClientProperty("AATextInfoPropertyKey", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void paintAll(final Graphics g) {
|
||||||
|
if (this.drawGraphics == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
super.paintAll(this.drawGraphics);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void paint(final Graphics g) {
|
||||||
|
if (this.drawGraphics == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
super.paint(this.drawGraphics);
|
||||||
|
}
|
||||||
|
catch (final Exception ex) {
|
||||||
|
LOGGER.warn(ex.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(final Graphics g) {
|
||||||
|
this.dirty = true;
|
||||||
|
|
||||||
|
if (this.drawGraphics == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
super.update(this.drawGraphics);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDirty() {
|
||||||
|
return this.dirty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDirty(final boolean dirty) {
|
||||||
|
this.dirty = dirty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DefaultTableModel getDefaultModel() {
|
||||||
|
return (DefaultTableModel) this.getModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Graphics getDrawGraphics() {
|
||||||
|
return this.drawGraphics;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDrawGraphics(final Graphics drawGraphics) {
|
||||||
|
this.drawGraphics = drawGraphics;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class CustomTableModel extends DefaultTableModel {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = -3590639586996686965L;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCellEditable(final int row, final int column) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package de.extio.game_engine.renderer.g2d.control.components;
|
||||||
|
|
||||||
|
import java.awt.Graphics;
|
||||||
|
import java.awt.TextArea;
|
||||||
|
|
||||||
|
import de.extio.game_engine.renderer.g2d.control.G2DControlHasExclusiveKeyEvent;
|
||||||
|
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
public class CustomTextArea extends TextArea implements G2DControlHasExclusiveKeyEvent {
|
||||||
|
|
||||||
|
protected boolean dirty = true;
|
||||||
|
|
||||||
|
public boolean isDirty() {
|
||||||
|
return this.dirty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDirty(final boolean dirty) {
|
||||||
|
this.dirty = dirty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CustomTextArea(final String text, final int rows, final int columns, final int scrollbars) {
|
||||||
|
super(text, rows, columns, scrollbars);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(final Graphics g) {
|
||||||
|
this.dirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package de.extio.game_engine.renderer.g2d.control.components;
|
||||||
|
|
||||||
|
import java.awt.Graphics;
|
||||||
|
import java.awt.TextField;
|
||||||
|
|
||||||
|
import de.extio.game_engine.renderer.g2d.control.G2DControlHasExclusiveKeyEvent;
|
||||||
|
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
public class CustomTextField extends TextField implements G2DControlHasExclusiveKeyEvent {
|
||||||
|
|
||||||
|
protected boolean dirty = true;
|
||||||
|
|
||||||
|
public boolean isDirty() {
|
||||||
|
return this.dirty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDirty(final boolean dirty) {
|
||||||
|
this.dirty = dirty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CustomTextField() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(final Graphics g) {
|
||||||
|
this.dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package de.extio.game_engine.renderer.g2d.control.components;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Graphics;
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.event.ActionListener;
|
||||||
|
|
||||||
|
import de.extio.game_engine.renderer.g2d.bo.rendering.G2DDrawFont;
|
||||||
|
import de.extio.game_engine.renderer.model.RgbaColor;
|
||||||
|
import de.extio.game_engine.spatial2.model.CoordI2;
|
||||||
|
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
public class CustomWindowCloseButton extends CustomAbstractButton {
|
||||||
|
|
||||||
|
public CustomWindowCloseButton(final boolean toggle, final ActionListener listener) {
|
||||||
|
super(toggle, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void paint(final Graphics g) {
|
||||||
|
final Graphics2D g2d = (Graphics2D) g;
|
||||||
|
|
||||||
|
final boolean highlight = (this.state & (STATE_HOVERED | STATE_TOGGLED)) != 0;
|
||||||
|
final boolean pressed = (this.state & STATE_PRESSED) != 0;
|
||||||
|
|
||||||
|
Color color = null;
|
||||||
|
if (pressed) {
|
||||||
|
color = ComponentRenderingSupport.COLOR_COMPONENT_SELECTED_0;
|
||||||
|
}
|
||||||
|
else if (highlight) {
|
||||||
|
color = ComponentRenderingSupport.COLOR_COMPONENT_BGR;
|
||||||
|
}
|
||||||
|
else if (this.backgroundColor == null) {
|
||||||
|
color = ComponentRenderingSupport.COLOR_COMPONENT_BORDER1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
color = this.backgroundColor;
|
||||||
|
}
|
||||||
|
g2d.setColor(color);
|
||||||
|
g2d.fillRect(0, 0, this.getWidth(), this.getHeight());
|
||||||
|
|
||||||
|
if (this.isEnabled()) {
|
||||||
|
if (pressed) {
|
||||||
|
g2d.setColor(RgbaColor.YELLOW.toAwtColor());
|
||||||
|
}
|
||||||
|
else if (highlight) {
|
||||||
|
g2d.setColor(ComponentRenderingSupport.COLOR_COMPONENT_SELECTED_1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
g2d.setColor(Color.BLACK);
|
||||||
|
}
|
||||||
|
final CoordI2 textDim = G2DDrawFont.getTextDimensions("X", g2d, 16, this.scaleFactor);
|
||||||
|
G2DDrawFont.renderText(g2d, textDim, 1.0, ((this.getWidth() - textDim.getX()) / 2), ((this.getHeight() - textDim.getY()) / 2), (int) (16 * this.scaleFactor), "X");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package de.extio.game_engine.renderer.g2d.control.impl;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import de.extio.game_engine.renderer.g2d.control.G2DDrawControl;
|
||||||
|
import de.extio.game_engine.renderer.model.bo.ControlRenderingBo.BaseControl;
|
||||||
|
import de.extio.game_engine.renderer.model.bo.ControlRenderingBo.SwitchControl;
|
||||||
|
import de.extio.game_engine.renderer.model.bo.ControlRenderingBo.ToggleButtonControl;
|
||||||
|
import de.extio.game_engine.renderer.model.event.UiControlEvent;
|
||||||
|
|
||||||
|
public abstract class G2DAbstractToggleButtonControlImpl extends G2DButtonControlImpl implements ToggleButtonControl, SwitchControl {
|
||||||
|
|
||||||
|
protected boolean drawBorder = true;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void performAction() {
|
||||||
|
if (this.enabled) {
|
||||||
|
this.manageCtrlGroupToggleState();
|
||||||
|
this.rendererData.getEventConsumer().accept(new UiControlEvent(this.id, this.control.isToggled()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isToggled() {
|
||||||
|
return this.control.isToggled();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setToggled(final boolean toggled) {
|
||||||
|
if (this.control != null && toggled != this.control.isToggled()) {
|
||||||
|
this.control.setToggled(toggled);
|
||||||
|
this.manageCtrlGroupToggleState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDrawBorder() {
|
||||||
|
return this.drawBorder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDrawBorder(final boolean draw) {
|
||||||
|
this.modified |= draw != this.drawBorder;
|
||||||
|
this.drawBorder = draw;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void manageCtrlGroupToggleState() {
|
||||||
|
final List<BaseControl> controls = G2DDrawControl.CONTROL_GROUPS.get(this.controlGroup);
|
||||||
|
if (controls != null && !controls.isEmpty()) {
|
||||||
|
boolean controlInGroupToggled = false;
|
||||||
|
|
||||||
|
for (final BaseControl control : controls) {
|
||||||
|
if (control instanceof G2DToggleButtonControlImpl) {
|
||||||
|
final G2DToggleButtonControlImpl tbControl = (G2DToggleButtonControlImpl) control;
|
||||||
|
if (tbControl.control == this.control) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
controlInGroupToggled |= tbControl.control.isToggled();
|
||||||
|
if (this.control.isToggled() && tbControl.control.isToggled()) {
|
||||||
|
tbControl.control.setToggled(!this.control.isToggled());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!controlInGroupToggled) {
|
||||||
|
this.control.setToggled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,227 @@
|
|||||||
|
package de.extio.game_engine.renderer.g2d.control.impl;
|
||||||
|
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.Transparency;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import de.extio.game_engine.renderer.g2d.G2DRenderer;
|
||||||
|
import de.extio.game_engine.renderer.g2d.G2DRenderingHintFactory;
|
||||||
|
import de.extio.game_engine.renderer.g2d.control.G2DDrawControlTooltip;
|
||||||
|
import de.extio.game_engine.renderer.g2d.control.G2DDrawControlTooltip.TooltipRecord;
|
||||||
|
import de.extio.game_engine.spatial2.WorldUtils2;
|
||||||
|
import de.extio.game_engine.spatial2.model.CoordI2;
|
||||||
|
import de.extio.game_engine.spatial2.model.ImmutableCoordI2;
|
||||||
|
import de.extio.game_engine.renderer.RendererData;
|
||||||
|
import de.extio.game_engine.renderer.model.bo.ControlRenderingBo.BaseControl;
|
||||||
|
|
||||||
|
public abstract class G2DBaseControlImpl implements BaseControl {
|
||||||
|
|
||||||
|
protected final static Logger LOGGER = LoggerFactory.getLogger(G2DBaseControlImpl.class);
|
||||||
|
|
||||||
|
protected int x;
|
||||||
|
|
||||||
|
protected int y;
|
||||||
|
|
||||||
|
protected int width;
|
||||||
|
|
||||||
|
protected int height;
|
||||||
|
|
||||||
|
protected int fontSize;
|
||||||
|
|
||||||
|
protected Graphics2D mainFrameGraphics;
|
||||||
|
|
||||||
|
protected RendererData rendererData;
|
||||||
|
|
||||||
|
protected String id;
|
||||||
|
|
||||||
|
protected boolean inUse;
|
||||||
|
|
||||||
|
protected String caption;
|
||||||
|
|
||||||
|
protected double scaleFactor;
|
||||||
|
|
||||||
|
protected boolean modified;
|
||||||
|
|
||||||
|
protected String controlGroup;
|
||||||
|
|
||||||
|
protected boolean visible;
|
||||||
|
|
||||||
|
protected boolean enabled;
|
||||||
|
|
||||||
|
protected BufferedImage bufferedImage;
|
||||||
|
|
||||||
|
protected Graphics2D bufferedImageGraphics;
|
||||||
|
|
||||||
|
protected String tooltip;
|
||||||
|
|
||||||
|
protected CoordI2 tooltipMousePosition;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BaseControl setInUse(final boolean inUse) {
|
||||||
|
this.inUse = inUse;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getInUse() {
|
||||||
|
return this.inUse;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BaseControl setId(final String id) {
|
||||||
|
this.id = id;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BaseControl setCaption(final String caption) {
|
||||||
|
this.modified |= caption != null && !Objects.equals(this.caption, caption);
|
||||||
|
this.caption = caption;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BaseControl setRendererData(final RendererData RendererData) {
|
||||||
|
this.rendererData = (RendererData) RendererData;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public G2DBaseControlImpl setMainFrameGraphics(final Graphics2D graphics) {
|
||||||
|
this.mainFrameGraphics = graphics;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BaseControl setX(final int x) {
|
||||||
|
this.modified |= this.x != x;
|
||||||
|
this.x = x;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BaseControl setY(final int y) {
|
||||||
|
this.modified |= this.y != y;
|
||||||
|
this.y = y;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BaseControl setWidth(final int width) {
|
||||||
|
this.modified |= this.width != width;
|
||||||
|
this.width = width;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BaseControl setHeight(final int height) {
|
||||||
|
this.modified |= this.height != height;
|
||||||
|
this.height = height;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BaseControl setFontSize(final int size) {
|
||||||
|
this.modified |= this.fontSize != size;
|
||||||
|
this.fontSize = size;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BaseControl setControlGroup(final String controlGroup) {
|
||||||
|
this.controlGroup = controlGroup;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getControlGroup() {
|
||||||
|
return this.controlGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BaseControl setVisible(final boolean visible) {
|
||||||
|
this.modified |= visible != this.visible;
|
||||||
|
this.visible = visible;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public G2DBaseControlImpl setScaleFactor(final double scaleFactor) {
|
||||||
|
this.modified = this.scaleFactor != scaleFactor;
|
||||||
|
this.scaleFactor = scaleFactor;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BaseControl setEnabled(final boolean enabled) {
|
||||||
|
this.modified |= enabled != this.enabled;
|
||||||
|
this.enabled = enabled;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BaseControl setTooltip(final String tooltip) {
|
||||||
|
this.tooltip = tooltip;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render() {
|
||||||
|
if (this.visible && this.bufferedImage != null) {
|
||||||
|
this.mainFrameGraphics.drawImage(this.bufferedImage, this.x, this.y, null);
|
||||||
|
this.drawToolTip();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.modified) {
|
||||||
|
this.modified = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void build() {
|
||||||
|
this.rebuildBufferedImage();
|
||||||
|
this.modified = false;
|
||||||
|
this.enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
this.disposeBufferedImage();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "BaseControlImpl [id=" + this.id + ", class=" + this.getClass().getSimpleName() + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void rebuildBufferedImage() {
|
||||||
|
this.disposeBufferedImage();
|
||||||
|
this.bufferedImage = ((G2DRenderer) this.rendererData.getRenderer()).getMainFrame().getGraphicsConfiguration().createCompatibleImage(Math.max(1, this.width), Math.max(1, this.height), Transparency.TRANSLUCENT);
|
||||||
|
this.bufferedImageGraphics = this.bufferedImage.createGraphics();
|
||||||
|
this.bufferedImageGraphics.setRenderingHints(G2DRenderingHintFactory.createDefault());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void disposeBufferedImage() {
|
||||||
|
if (this.bufferedImageGraphics != null) {
|
||||||
|
this.bufferedImageGraphics.dispose();
|
||||||
|
this.bufferedImageGraphics = null;
|
||||||
|
}
|
||||||
|
if (this.bufferedImage != null) {
|
||||||
|
this.bufferedImage.flush();
|
||||||
|
this.bufferedImage = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void drawToolTip() {
|
||||||
|
if (this.enabled &&
|
||||||
|
this.tooltip != null &&
|
||||||
|
this.tooltipMousePosition != null &&
|
||||||
|
WorldUtils2.isInBounds(this.tooltipMousePosition, ImmutableCoordI2.create(Math.max(1, this.width), Math.max(1, this.height)))) {
|
||||||
|
|
||||||
|
G2DDrawControlTooltip.TOOLTIP = new TooltipRecord(ImmutableCoordI2.create(this.x + 10 + this.tooltipMousePosition.getX(), this.y + 10 + this.tooltipMousePosition.getY()), this.tooltip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
package de.extio.game_engine.renderer.g2d.control.impl;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import de.extio.game_engine.renderer.g2d.G2DRenderer;
|
||||||
|
import de.extio.game_engine.renderer.g2d.control.components.CustomAbstractButton;
|
||||||
|
import de.extio.game_engine.renderer.g2d.control.components.CustomButton;
|
||||||
|
import de.extio.game_engine.renderer.model.bo.ControlRenderingBo.BaseControl;
|
||||||
|
import de.extio.game_engine.renderer.model.bo.ControlRenderingBo.ButtonControl;
|
||||||
|
import de.extio.game_engine.renderer.model.event.UiControlEvent;
|
||||||
|
import de.extio.game_engine.renderer.model.RgbaColor;
|
||||||
|
|
||||||
|
public class G2DButtonControlImpl extends G2DBaseControlImpl implements ButtonControl {
|
||||||
|
|
||||||
|
protected CustomAbstractButton control;
|
||||||
|
|
||||||
|
protected String iconResourceName;
|
||||||
|
|
||||||
|
private RgbaColor backgroundColor;
|
||||||
|
|
||||||
|
private boolean positionModified;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void build() {
|
||||||
|
super.build();
|
||||||
|
|
||||||
|
this.createControl();
|
||||||
|
this.initControl();
|
||||||
|
((G2DRenderer) this.rendererData.getRenderer()).getMainFrame().add(this.control);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void performAction() {
|
||||||
|
if (this.enabled) {
|
||||||
|
this.rendererData.getEventConsumer().accept(new UiControlEvent(this.id, null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render() {
|
||||||
|
if (this.modified || this.control.isDirty()) {
|
||||||
|
this.initControl();
|
||||||
|
this.rebuildBufferedImage();
|
||||||
|
this.control.paint(this.bufferedImageGraphics);
|
||||||
|
this.control.setDirty(false);
|
||||||
|
}
|
||||||
|
else if (this.positionModified) {
|
||||||
|
this.control.setLocation(this.x, this.y);
|
||||||
|
this.positionModified = false;
|
||||||
|
}
|
||||||
|
this.tooltipMousePosition = this.control.getLastMousePosition();
|
||||||
|
|
||||||
|
super.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
if (this.control.isFocusOwner()) {
|
||||||
|
((G2DRenderer) this.rendererData.getRenderer()).getMainFrame().requestFocus();
|
||||||
|
}
|
||||||
|
this.control.setVisible(false);
|
||||||
|
this.control.invalidate();
|
||||||
|
this.control.close();
|
||||||
|
((G2DRenderer) this.rendererData.getRenderer()).getMainFrame().remove(this.control);
|
||||||
|
|
||||||
|
super.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void createControl() {
|
||||||
|
this.control = new CustomButton(false, event -> this.performAction());
|
||||||
|
this.control.setName(this.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void initControl() {
|
||||||
|
this.control.setLocation(this.x, this.y);
|
||||||
|
this.positionModified = false;
|
||||||
|
this.control.setSize(this.width, this.height);
|
||||||
|
this.control.setCaption(this.caption);
|
||||||
|
this.control.setFontSize(this.fontSize);
|
||||||
|
this.control.setScaleFactor(this.scaleFactor);
|
||||||
|
this.control.setEnabled(this.enabled);
|
||||||
|
this.control.setVisible(this.visible);
|
||||||
|
this.control.setIconResourceName(this.iconResourceName);
|
||||||
|
if (this.backgroundColor == null) {
|
||||||
|
this.control.setBackgroundColor(null);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.control.setBackgroundColor(this.backgroundColor.toAwtColor());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BaseControl setX(final int x) {
|
||||||
|
this.positionModified |= this.x != x;
|
||||||
|
this.x = x;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BaseControl setY(final int y) {
|
||||||
|
this.positionModified |= this.y != y;
|
||||||
|
this.y = y;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setIconResourceName(final String iconResourceName) {
|
||||||
|
this.modified |= !Objects.equals(this.iconResourceName, iconResourceName);
|
||||||
|
this.iconResourceName = iconResourceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getIconResourceName() {
|
||||||
|
return this.iconResourceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RgbaColor getBackgroundColor() {
|
||||||
|
return this.backgroundColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBackgroundColor(final RgbaColor backgroundColor) {
|
||||||
|
this.modified |= !Objects.equals(this.backgroundColor, backgroundColor);
|
||||||
|
this.backgroundColor = backgroundColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
package de.extio.game_engine.renderer.g2d.control.impl;
|
||||||
|
|
||||||
|
import de.extio.game_engine.renderer.g2d.G2DRenderer;
|
||||||
|
import de.extio.game_engine.renderer.g2d.control.components.CustomLabel;
|
||||||
|
import de.extio.game_engine.renderer.model.bo.ControlRenderingBo.LabelControl;
|
||||||
|
import de.extio.game_engine.renderer.model.event.UiControlEvent;
|
||||||
|
import de.extio.game_engine.renderer.model.DrawFontRenderingBoTextAlignment;
|
||||||
|
import de.extio.game_engine.renderer.model.RgbaColor;
|
||||||
|
|
||||||
|
public class G2DLabelControlImpl extends G2DBaseControlImpl implements LabelControl {
|
||||||
|
|
||||||
|
protected CustomLabel control;
|
||||||
|
|
||||||
|
protected RgbaColor backgroundColor;
|
||||||
|
|
||||||
|
protected RgbaColor foregroundColor;
|
||||||
|
|
||||||
|
protected DrawFontRenderingBoTextAlignment textAlignment;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RgbaColor getBackgroundColor() {
|
||||||
|
return this.backgroundColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBackgroundColor(final RgbaColor color) {
|
||||||
|
this.modified |= (color == null) ? this.backgroundColor != null : !color.equals(this.backgroundColor);
|
||||||
|
this.backgroundColor = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RgbaColor getForegroundColor() {
|
||||||
|
return this.foregroundColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setForegroundColor(final RgbaColor color) {
|
||||||
|
this.modified |= (color != null) && !color.equals(this.foregroundColor);
|
||||||
|
this.foregroundColor = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DrawFontRenderingBoTextAlignment getTextAlignment() {
|
||||||
|
return this.textAlignment;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setTextAlignment(final DrawFontRenderingBoTextAlignment alignment) {
|
||||||
|
this.modified |= alignment != this.textAlignment;
|
||||||
|
this.textAlignment = alignment;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void build() {
|
||||||
|
super.build();
|
||||||
|
|
||||||
|
this.createControl();
|
||||||
|
this.initControl();
|
||||||
|
((G2DRenderer) this.rendererData.getRenderer()).getMainFrame().add(this.control);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void performAction() {
|
||||||
|
if (this.enabled) {
|
||||||
|
this.rendererData.getEventConsumer().accept(new UiControlEvent(this.id, null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render() {
|
||||||
|
if (this.modified || this.control.isDirty()) {
|
||||||
|
//LOGGER.debug("Label modified " + this.id);
|
||||||
|
this.initControl();
|
||||||
|
this.rebuildBufferedImage();
|
||||||
|
this.control.paint(this.bufferedImageGraphics);
|
||||||
|
this.control.setDirty(false);
|
||||||
|
}
|
||||||
|
if (this.tooltip != null) {
|
||||||
|
this.tooltipMousePosition = this.control.getLastMousePosition();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.tooltipMousePosition = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
super.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
this.control.setVisible(false);
|
||||||
|
this.control.invalidate();
|
||||||
|
((G2DRenderer) this.rendererData.getRenderer()).getMainFrame().remove(this.control);
|
||||||
|
|
||||||
|
super.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void createControl() {
|
||||||
|
this.control = new CustomLabel(event -> this.performAction());
|
||||||
|
this.control.setName(this.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void initControl() {
|
||||||
|
this.control.setLocation(this.x, this.y);
|
||||||
|
this.control.setSize(this.width, this.height);
|
||||||
|
this.control.setCaption(this.caption);
|
||||||
|
this.control.setFontSize(this.fontSize);
|
||||||
|
this.control.setScaleFactor(this.scaleFactor);
|
||||||
|
this.control.setBackgroundColor(this.backgroundColor != null ? this.backgroundColor.toAwtColor() : null);
|
||||||
|
this.control.setForegroundColor(this.foregroundColor != null ? this.foregroundColor.toAwtColor() : null);
|
||||||
|
this.control.setEnabled(this.enabled);
|
||||||
|
this.control.setVisible(this.visible);
|
||||||
|
this.control.setTextAlignment(this.getTextAlignment());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package de.extio.game_engine.renderer.g2d.control.impl;
|
||||||
|
|
||||||
|
import java.awt.Component;
|
||||||
|
|
||||||
|
import de.extio.game_engine.renderer.g2d.G2DRenderer;
|
||||||
|
import de.extio.game_engine.renderer.model.bo.ControlRenderingBo.SetFocusControl;
|
||||||
|
|
||||||
|
public class G2DSetFocusControl extends G2DBaseControlImpl implements SetFocusControl {
|
||||||
|
|
||||||
|
protected String focusId;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getFocusId() {
|
||||||
|
return this.focusId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFocusId(final String id) {
|
||||||
|
this.focusId = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void build() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void performAction() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render() {
|
||||||
|
if (isNullOrEmpty(this.focusId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Component[] components = ((G2DRenderer) this.rendererData.getRenderer()).getMainFrame().getComponents();
|
||||||
|
for (int i = 0; i < components.length; i++) {
|
||||||
|
if (this.focusId.equals(components[i].getName())) {
|
||||||
|
components[i].requestFocus();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
super.close();
|
||||||
|
|
||||||
|
this.setFocusId(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isNullOrEmpty(final String value) {
|
||||||
|
return value == null || value.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
package de.extio.game_engine.renderer.g2d.control.impl;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import de.extio.game_engine.renderer.g2d.G2DRenderer;
|
||||||
|
import de.extio.game_engine.renderer.g2d.control.components.CustomSlider;
|
||||||
|
import de.extio.game_engine.renderer.model.bo.ControlRenderingBo.SliderControl;
|
||||||
|
import de.extio.game_engine.renderer.model.event.UiControlEvent;
|
||||||
|
import de.extio.game_engine.renderer.model.RgbaColor;
|
||||||
|
|
||||||
|
public class G2DSliderControlImpl extends G2DBaseControlImpl implements SliderControl {
|
||||||
|
|
||||||
|
protected CustomSlider control;
|
||||||
|
|
||||||
|
protected RgbaColor color;
|
||||||
|
|
||||||
|
protected boolean horizontal;
|
||||||
|
|
||||||
|
protected double lastValue;
|
||||||
|
|
||||||
|
protected double lastValue2;
|
||||||
|
|
||||||
|
protected Consumer<Double> actionConsumer;
|
||||||
|
|
||||||
|
public G2DSliderControlImpl() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public G2DSliderControlImpl(final Consumer<Double> actionConsumer) {
|
||||||
|
this.actionConsumer = actionConsumer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RgbaColor getColor() {
|
||||||
|
return this.color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setColor(final RgbaColor color) {
|
||||||
|
this.modified |= (color != null) && !color.equals(this.color);
|
||||||
|
this.color = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getValue() {
|
||||||
|
return this.lastValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setValue(final double value) {
|
||||||
|
this.modified |= value != this.lastValue;
|
||||||
|
this.lastValue = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getValue2() {
|
||||||
|
return this.lastValue2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setValue2(final double value2) {
|
||||||
|
this.modified |= value2 != this.lastValue2;
|
||||||
|
this.lastValue2 = value2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isHorizontal() {
|
||||||
|
return this.horizontal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setHorizontal(final boolean horizontal) {
|
||||||
|
this.modified |= horizontal != this.horizontal;
|
||||||
|
this.horizontal = horizontal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void build() {
|
||||||
|
super.build();
|
||||||
|
|
||||||
|
this.createControl();
|
||||||
|
this.initControl();
|
||||||
|
((G2DRenderer) this.rendererData.getRenderer()).getMainFrame().add(this.control);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void performAction() {
|
||||||
|
if (this.enabled) {
|
||||||
|
if (this.actionConsumer == null) {
|
||||||
|
this.rendererData.getEventConsumer().accept(new UiControlEvent(this.id, this.lastValue));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.actionConsumer.accept(this.lastValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render() {
|
||||||
|
this.control.releaseEvents();
|
||||||
|
|
||||||
|
if (this.modified || this.control.isDirty()) {
|
||||||
|
this.initControl();
|
||||||
|
this.rebuildBufferedImage();
|
||||||
|
this.control.paint(this.bufferedImageGraphics);
|
||||||
|
this.control.setDirty(false);
|
||||||
|
}
|
||||||
|
if (this.tooltip != null) {
|
||||||
|
this.tooltipMousePosition = this.control.getLastMousePosition();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.tooltipMousePosition = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
super.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
if (this.control.isFocusOwner()) {
|
||||||
|
((G2DRenderer) this.rendererData.getRenderer()).getMainFrame().requestFocus();
|
||||||
|
}
|
||||||
|
this.control.setVisible(false);
|
||||||
|
this.control.invalidate();
|
||||||
|
((G2DRenderer) this.rendererData.getRenderer()).getMainFrame().remove(this.control);
|
||||||
|
|
||||||
|
super.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void createControl() {
|
||||||
|
this.control = new CustomSlider(event -> {
|
||||||
|
this.lastValue = Double.parseDouble(event.getActionCommand());
|
||||||
|
if (this.enabled) {
|
||||||
|
this.performAction();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.control.setName(this.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void initControl() {
|
||||||
|
this.control.setLocation(this.x, this.y);
|
||||||
|
this.control.setSize(this.width, this.height);
|
||||||
|
this.control.setScaleFactor(this.scaleFactor);
|
||||||
|
this.control.setColor(this.color != null ? this.color.toAwtColor() : null);
|
||||||
|
this.control.setHorizontal(this.horizontal);
|
||||||
|
this.control.setValue(this.lastValue);
|
||||||
|
this.control.setValue2(this.lastValue2);
|
||||||
|
this.control.setEnabled(this.enabled);
|
||||||
|
this.control.setVisible(this.visible);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package de.extio.game_engine.renderer.g2d.control.impl;
|
||||||
|
|
||||||
|
import de.extio.game_engine.renderer.g2d.control.components.CustomSwitch;
|
||||||
|
|
||||||
|
public class G2DSwitchControlImpl extends G2DAbstractToggleButtonControlImpl {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void createControl() {
|
||||||
|
this.control = new CustomSwitch(true, event -> this.performAction());
|
||||||
|
this.control.setName(this.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void initControl() {
|
||||||
|
super.initControl();
|
||||||
|
((CustomSwitch) this.control).setDrawBorder(this.drawBorder);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,285 @@
|
|||||||
|
package de.extio.game_engine.renderer.g2d.control.impl;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Component;
|
||||||
|
import java.awt.EventQueue;
|
||||||
|
import java.awt.Font;
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.event.MouseAdapter;
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.awt.event.MouseWheelEvent;
|
||||||
|
import java.awt.event.MouseWheelListener;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.swing.JTable;
|
||||||
|
import javax.swing.ListSelectionModel;
|
||||||
|
import javax.swing.table.DefaultTableCellRenderer;
|
||||||
|
|
||||||
|
import de.extio.game_engine.renderer.g2d.G2DRenderer;
|
||||||
|
import de.extio.game_engine.renderer.g2d.control.components.ComponentRenderingSupport;
|
||||||
|
import de.extio.game_engine.renderer.g2d.control.components.CustomTable;
|
||||||
|
import de.extio.game_engine.renderer.g2d.bo.rendering.G2DDrawFont;
|
||||||
|
import de.extio.game_engine.renderer.RendererData;
|
||||||
|
import de.extio.game_engine.renderer.model.bo.ControlRenderingBo.BaseControl;
|
||||||
|
import de.extio.game_engine.renderer.model.bo.ControlRenderingBo.TableControl;
|
||||||
|
import de.extio.game_engine.renderer.model.event.UiControlEvent;
|
||||||
|
import de.extio.game_engine.renderer.model.RgbaColor;
|
||||||
|
import de.extio.game_engine.spatial2.model.ImmutableCoordI2;
|
||||||
|
|
||||||
|
public class G2DTableControlImpl extends G2DBaseControlImpl implements TableControl {
|
||||||
|
|
||||||
|
protected CustomTable control;
|
||||||
|
|
||||||
|
protected G2DSliderControlImpl scrollbar;
|
||||||
|
|
||||||
|
protected List<Object> data;
|
||||||
|
|
||||||
|
protected int rows;
|
||||||
|
|
||||||
|
protected long version;
|
||||||
|
|
||||||
|
protected boolean dataModified;
|
||||||
|
|
||||||
|
protected boolean scrollPositionModified;
|
||||||
|
|
||||||
|
protected double scrollPositionPerc = 0.0;
|
||||||
|
|
||||||
|
private boolean firstColDoubleSize;
|
||||||
|
|
||||||
|
public G2DTableControlImpl() {
|
||||||
|
this.scrollbar = new G2DSliderControlImpl(value -> {
|
||||||
|
this.onScroll(value.doubleValue());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void build() {
|
||||||
|
super.build();
|
||||||
|
|
||||||
|
this.createControl();
|
||||||
|
this.initControl();
|
||||||
|
((G2DRenderer) this.rendererData.getRenderer()).getMainFrame().add(this.control);
|
||||||
|
|
||||||
|
this.scrollbar.setColor(RgbaColor.LIGHT_GRAY);
|
||||||
|
this.scrollbar.setHorizontal(false);
|
||||||
|
this.scrollbar.setValue(1.0);
|
||||||
|
this.scrollbar.setValue2(1.0);
|
||||||
|
this.scrollbar.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void performAction() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render() {
|
||||||
|
if (this.modified || this.control.isDirty() || this.scrollPositionModified) {
|
||||||
|
this.rebuildBufferedImage();
|
||||||
|
this.initControl();
|
||||||
|
this.control.setDirty(false);
|
||||||
|
|
||||||
|
}
|
||||||
|
this.control.paint(this.bufferedImageGraphics);
|
||||||
|
|
||||||
|
this.scrollbar.render();
|
||||||
|
|
||||||
|
super.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
if (this.control.isFocusOwner()) {
|
||||||
|
((G2DRenderer) this.rendererData.getRenderer()).getMainFrame().requestFocus();
|
||||||
|
}
|
||||||
|
this.control.setVisible(false);
|
||||||
|
this.control.invalidate();
|
||||||
|
((G2DRenderer) this.rendererData.getRenderer()).getMainFrame().remove(this.control);
|
||||||
|
|
||||||
|
super.close();
|
||||||
|
|
||||||
|
this.scrollbar.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
protected void createControl() {
|
||||||
|
this.control = new CustomTable();
|
||||||
|
this.control.setName(this.id);
|
||||||
|
this.control.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||||
|
this.control.addMouseListener(new MouseAdapter() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mousePressed(final MouseEvent e) {
|
||||||
|
final int row = G2DTableControlImpl.this.control.rowAtPoint(e.getPoint());
|
||||||
|
final int col = G2DTableControlImpl.this.control.columnAtPoint(e.getPoint());
|
||||||
|
if (row > -1 && col > -1) {
|
||||||
|
if (G2DTableControlImpl.this.enabled) {
|
||||||
|
G2DTableControlImpl.this.rendererData.getEventConsumer().accept(new UiControlEvent(G2DTableControlImpl.this.id, ImmutableCoordI2.create(row, col)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.control.addMouseWheelListener(new MouseWheelListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseWheelMoved(final MouseWheelEvent e) {
|
||||||
|
if (e.getWheelRotation() == -1) {
|
||||||
|
final int displayRows = G2DTableControlImpl.this.control.getHeight() / G2DTableControlImpl.this.control.getRowHeight();
|
||||||
|
G2DTableControlImpl.this.scrollbar.setValue(Math.min(1.0, Math.max(0.0, G2DTableControlImpl.this.scrollbar.getValue() + ((double) displayRows / G2DTableControlImpl.this.rows / 2.0))));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
final int displayRows = G2DTableControlImpl.this.control.getHeight() / G2DTableControlImpl.this.control.getRowHeight();
|
||||||
|
G2DTableControlImpl.this.scrollbar.setValue(Math.min(1.0, Math.max(0.0, G2DTableControlImpl.this.scrollbar.getValue() - ((double) displayRows / G2DTableControlImpl.this.rows / 2.0))));
|
||||||
|
}
|
||||||
|
|
||||||
|
G2DTableControlImpl.this.onScroll(G2DTableControlImpl.this.scrollbar.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
this.control.setDefaultRenderer(Object.class, new DefaultTableCellRenderer() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Component getTableCellRendererComponent(final JTable table, final Object value, final boolean isSelected, final boolean hasFocus, final int row, final int column) {
|
||||||
|
if (row == 0) {
|
||||||
|
final Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
|
||||||
|
component.setFont(component.getFont().deriveFont(Font.BOLD));
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
return super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.dataModified = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void initControl() {
|
||||||
|
// LOGGER.debug("Table modified " + this.id);
|
||||||
|
|
||||||
|
this.control.setDrawGraphics(this.bufferedImageGraphics);
|
||||||
|
EventQueue.invokeLater(() -> {
|
||||||
|
this.control.setLocation(this.x, this.y);
|
||||||
|
// this.control.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
|
||||||
|
this.control.setSize(this.width - 30, this.height);
|
||||||
|
this.control.setEnabled(this.enabled);
|
||||||
|
this.control.setVisible(this.visible);
|
||||||
|
this.control.setFont(G2DDrawFont.getFont(this.scaleFactor, this.fontSize));
|
||||||
|
this.control.setRowHeight((int) (this.control.getFont().getSize2D() * 1.25));
|
||||||
|
|
||||||
|
this.control.setBackground(ComponentRenderingSupport.COLOR_COMPONENT_BGR);
|
||||||
|
this.control.setForeground(Color.WHITE);
|
||||||
|
|
||||||
|
if (this.dataModified || this.scrollPositionModified) {
|
||||||
|
// LOGGER.debug("Table data modified " + this.id);
|
||||||
|
if (this.rows == 0 || this.data == null || this.data.isEmpty()) {
|
||||||
|
this.control.getDefaultModel().setRowCount(0);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
final int actualColumns = this.data.size() / this.rows;
|
||||||
|
this.control.getDefaultModel().setColumnCount(actualColumns);
|
||||||
|
if (this.firstColDoubleSize) {
|
||||||
|
for (int i = 0; i < actualColumns; i++) {
|
||||||
|
this.control.getColumnModel().getColumn(i).setPreferredWidth(i == 0 ? 200 : 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final int actualRows = (int) ((1.0 - this.scrollPositionPerc) * this.rows);
|
||||||
|
this.control.getDefaultModel().setRowCount(actualRows);
|
||||||
|
final int offset = (this.rows - actualRows) * actualColumns;
|
||||||
|
for (int i = 0; i < this.data.size() - offset; i++) {
|
||||||
|
this.control.getDefaultModel().setValueAt(this.data.get(i + offset), i / this.control.getDefaultModel().getColumnCount(), i % this.control.getDefaultModel().getColumnCount());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dataModified = false;
|
||||||
|
this.scrollPositionModified = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.scrollbar.setX(this.x + this.width - 30);
|
||||||
|
this.scrollbar.setY(this.y);
|
||||||
|
this.scrollbar.setWidth(30);
|
||||||
|
this.scrollbar.setHeight(this.height);
|
||||||
|
this.scrollbar.setScaleFactor(this.scaleFactor);
|
||||||
|
this.scrollbar.setEnabled(this.enabled);
|
||||||
|
this.scrollbar.setVisible(this.visible);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onScroll(final double value) {
|
||||||
|
this.scrollPositionPerc = 1.0 - value;
|
||||||
|
this.scrollPositionModified = true;
|
||||||
|
this.scrollbar.setValue2(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public G2DBaseControlImpl setScaleFactor(final double scaleFactor) {
|
||||||
|
this.scrollbar.setScaleFactor(scaleFactor);
|
||||||
|
return super.setScaleFactor(scaleFactor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BaseControl setControlGroup(final String controlGroup) {
|
||||||
|
this.scrollbar.setControlGroup(controlGroup);
|
||||||
|
return super.setControlGroup(controlGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BaseControl setRendererData(final RendererData RendererData) {
|
||||||
|
this.scrollbar.setRendererData(RendererData);
|
||||||
|
return super.setRendererData(RendererData);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public G2DBaseControlImpl setMainFrameGraphics(final Graphics2D graphics) {
|
||||||
|
this.scrollbar.setMainFrameGraphics(graphics);
|
||||||
|
return super.setMainFrameGraphics(graphics);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BaseControl setId(final String id) {
|
||||||
|
this.scrollbar.setId(id + "_scroll");
|
||||||
|
return super.setId(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Object> getData() {
|
||||||
|
return this.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setData(final List<Object> data) {
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getRows() {
|
||||||
|
return this.rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRows(final int rows) {
|
||||||
|
this.rows = rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getVersion() {
|
||||||
|
return this.version;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setVersion(final long version) {
|
||||||
|
this.modified |= version != this.version;
|
||||||
|
this.dataModified |= version != this.version;
|
||||||
|
this.version = version;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFirstColDoubleSize() {
|
||||||
|
return this.firstColDoubleSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setFirstColDoubleSize(final boolean firstColDoubleSize) {
|
||||||
|
this.firstColDoubleSize = firstColDoubleSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
package de.extio.game_engine.renderer.g2d.control.impl;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.TextArea;
|
||||||
|
import java.awt.TextComponent;
|
||||||
|
import java.awt.TextField;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import de.extio.game_engine.renderer.g2d.G2DRenderer;
|
||||||
|
import de.extio.game_engine.renderer.g2d.control.components.ComponentRenderingSupport;
|
||||||
|
import de.extio.game_engine.renderer.g2d.control.components.CustomTextArea;
|
||||||
|
import de.extio.game_engine.renderer.g2d.control.components.CustomTextField;
|
||||||
|
import de.extio.game_engine.renderer.g2d.bo.rendering.G2DDrawFont;
|
||||||
|
import de.extio.game_engine.renderer.model.bo.ControlRenderingBo.BaseControl;
|
||||||
|
import de.extio.game_engine.renderer.model.bo.ControlRenderingBo.TextfieldControl;
|
||||||
|
import de.extio.game_engine.renderer.model.event.UiControlEvent;
|
||||||
|
import de.extio.game_engine.renderer.model.RgbaColor;
|
||||||
|
|
||||||
|
public class G2DTextfieldControlImpl extends G2DBaseControlImpl implements TextfieldControl {
|
||||||
|
|
||||||
|
private TextComponent textComponent;
|
||||||
|
|
||||||
|
private boolean multiLine;
|
||||||
|
|
||||||
|
private String lastText;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isMultiLine() {
|
||||||
|
return this.multiLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setMultiLine(final boolean multiLine) {
|
||||||
|
if (this.textComponent != null) {
|
||||||
|
throw new UnsupportedOperationException("Multiline property is read-only after initial control creation");
|
||||||
|
}
|
||||||
|
this.multiLine = multiLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BaseControl setCaption(final String caption) {
|
||||||
|
this.modified |= caption != null && (!Objects.equals(this.caption, caption) || !Objects.equals(this.textComponent.getText(), caption));
|
||||||
|
this.caption = caption;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void build() {
|
||||||
|
super.build();
|
||||||
|
|
||||||
|
if (this.multiLine) {
|
||||||
|
this.textComponent = new CustomTextArea("", 1, 1, TextArea.SCROLLBARS_VERTICAL_ONLY);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.textComponent = new CustomTextField();
|
||||||
|
}
|
||||||
|
this.textComponent.setName(this.id);
|
||||||
|
this.initTextField();
|
||||||
|
|
||||||
|
if (!this.multiLine) {
|
||||||
|
((TextField) this.textComponent).addActionListener(event -> {
|
||||||
|
// Simulate pressing enter
|
||||||
|
this.rendererData.getEventConsumer().accept(new UiControlEvent(this.id, (this.lastText != null ? this.lastText : "") + "\n"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
((G2DRenderer) this.rendererData.getRenderer()).getMainFrame().add(this.textComponent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void performAction() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render() {
|
||||||
|
final String curComponentText = this.textComponent.getText();
|
||||||
|
if (!Objects.equals(this.lastText, curComponentText) && !((this.lastText == null || this.lastText.isEmpty()) && (curComponentText == null || curComponentText.isEmpty()))) {
|
||||||
|
this.lastText = curComponentText;
|
||||||
|
this.rendererData.getEventConsumer().accept(new UiControlEvent(this.id, this.lastText));
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean componentDirty;
|
||||||
|
if (this.multiLine) {
|
||||||
|
componentDirty = ((CustomTextArea) this.textComponent).isDirty();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
componentDirty = ((CustomTextField) this.textComponent).isDirty();
|
||||||
|
}
|
||||||
|
if (this.modified || componentDirty) {
|
||||||
|
//LOGGER.debug("TextComponent modified " + this.id);
|
||||||
|
this.initTextField();
|
||||||
|
this.rebuildBufferedImage();
|
||||||
|
this.safeInvoke(() -> this.textComponent.paint(this.bufferedImageGraphics));
|
||||||
|
if (this.multiLine) {
|
||||||
|
((CustomTextArea) this.textComponent).setDirty(false);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
((CustomTextField) this.textComponent).setDirty(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
super.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
if (this.textComponent.isFocusOwner()) {
|
||||||
|
((G2DRenderer) this.rendererData.getRenderer()).getMainFrame().requestFocus();
|
||||||
|
}
|
||||||
|
this.safeInvoke(() -> this.textComponent.setVisible(false));
|
||||||
|
this.safeInvoke(() -> this.textComponent.invalidate());
|
||||||
|
((G2DRenderer) this.rendererData.getRenderer()).getMainFrame().remove(this.textComponent);
|
||||||
|
|
||||||
|
super.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initTextField() {
|
||||||
|
this.safeInvoke(() -> this.textComponent.setBackground(ComponentRenderingSupport.COLOR_COMPONENT_BGR));
|
||||||
|
this.safeInvoke(() -> this.textComponent.setForeground(Color.WHITE));
|
||||||
|
this.safeInvoke(() -> this.textComponent.setLocation(this.x, this.y));
|
||||||
|
this.safeInvoke(() -> this.textComponent.setSize(this.width, this.height));
|
||||||
|
this.safeInvoke(() -> this.textComponent.setFont(G2DDrawFont.getFont(this.scaleFactor, this.fontSize)));
|
||||||
|
this.safeInvoke(() -> this.textComponent.setVisible(this.visible));
|
||||||
|
this.safeInvoke(() -> this.textComponent.setEditable(this.enabled));
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (this.caption != null && !this.textComponent.getText().equals(this.caption)) {
|
||||||
|
final int pos = this.textComponent.getCaretPosition();
|
||||||
|
this.safeInvoke(() -> this.textComponent.setText(this.caption));
|
||||||
|
this.safeInvoke(() -> this.textComponent.setCaretPosition(pos));
|
||||||
|
this.lastText = this.caption;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (final Exception exc) {
|
||||||
|
this.modified = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.multiLine) {
|
||||||
|
this.safeInvoke(() -> this.textComponent.setEditable(false));
|
||||||
|
this.safeInvoke(() -> this.textComponent.setFocusable(false));
|
||||||
|
final int scrollPos = (this.caption != null ? this.caption.lastIndexOf('\n') : -1) + 1;
|
||||||
|
this.safeInvoke(() -> this.textComponent.setCaretPosition(scrollPos));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void safeInvoke(final Runnable runnable) {
|
||||||
|
try {
|
||||||
|
runnable.run();
|
||||||
|
}
|
||||||
|
catch (final Exception exc) {
|
||||||
|
LOGGER.warn(exc.getMessage(), exc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RgbaColor getBackgroundColor() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBackgroundColor(final RgbaColor color) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isReadonly() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setReadonly(final boolean readonly) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,361 @@
|
|||||||
|
package de.extio.game_engine.renderer.g2d.control.impl;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.event.MouseWheelEvent;
|
||||||
|
import java.awt.event.MouseWheelListener;
|
||||||
|
|
||||||
|
import javax.swing.JTextField;
|
||||||
|
import javax.swing.text.JTextComponent;
|
||||||
|
|
||||||
|
import de.extio.game_engine.renderer.g2d.G2DRenderer;
|
||||||
|
import de.extio.game_engine.renderer.g2d.control.components.ComponentRenderingSupport;
|
||||||
|
import de.extio.game_engine.renderer.g2d.control.components.CustomJTextArea;
|
||||||
|
import de.extio.game_engine.renderer.g2d.control.components.CustomJTextField;
|
||||||
|
import de.extio.game_engine.renderer.g2d.bo.rendering.G2DDrawFont;
|
||||||
|
import de.extio.game_engine.renderer.RendererData;
|
||||||
|
import de.extio.game_engine.renderer.model.bo.ControlRenderingBo.BaseControl;
|
||||||
|
import de.extio.game_engine.renderer.model.bo.ControlRenderingBo.TextfieldControl;
|
||||||
|
import de.extio.game_engine.renderer.model.event.UiControlEvent;
|
||||||
|
import de.extio.game_engine.renderer.model.RgbaColor;
|
||||||
|
|
||||||
|
public class G2DTextfieldControlImpl2 extends G2DBaseControlImpl implements TextfieldControl {
|
||||||
|
|
||||||
|
private JTextComponent textComponent;
|
||||||
|
|
||||||
|
// private CustomJScrollPane scrollPane;
|
||||||
|
|
||||||
|
private final G2DSliderControlImpl scrollbar;
|
||||||
|
|
||||||
|
private boolean multiLine;
|
||||||
|
|
||||||
|
private boolean readonly;
|
||||||
|
|
||||||
|
private String lastCaption = "";
|
||||||
|
|
||||||
|
private String lastText = "";
|
||||||
|
|
||||||
|
protected RgbaColor backgroundColor;
|
||||||
|
|
||||||
|
private boolean scrollPositionModified;
|
||||||
|
|
||||||
|
private double scrollPositionPerc = 0.0;
|
||||||
|
|
||||||
|
public G2DTextfieldControlImpl2() {
|
||||||
|
this.scrollbar = new G2DSliderControlImpl(value -> {
|
||||||
|
this.onScroll(value.doubleValue());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RgbaColor getBackgroundColor() {
|
||||||
|
return this.backgroundColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBackgroundColor(final RgbaColor color) {
|
||||||
|
this.modified |= (color == null) ? this.backgroundColor != null : !color.equals(this.backgroundColor);
|
||||||
|
this.backgroundColor = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isMultiLine() {
|
||||||
|
return this.multiLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setMultiLine(final boolean multiLine) {
|
||||||
|
if (this.textComponent != null) {
|
||||||
|
throw new UnsupportedOperationException("Multiline property is read-only after initial control creation");
|
||||||
|
}
|
||||||
|
this.multiLine = multiLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isReadonly() {
|
||||||
|
return this.readonly;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setReadonly(final boolean readonly) {
|
||||||
|
this.modified |= readonly != this.readonly;
|
||||||
|
this.readonly = readonly;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BaseControl setCaption(final String caption) {
|
||||||
|
if (this.multiLine) {
|
||||||
|
this.modified |= caption != null && !caption.equals(this.caption);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.modified |= caption != null && (!caption.equals(this.caption) || !caption.equals(this.textComponent.getText()));
|
||||||
|
}
|
||||||
|
if (caption != null) {
|
||||||
|
this.lastCaption = caption;
|
||||||
|
}
|
||||||
|
this.caption = caption;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void build() {
|
||||||
|
super.build();
|
||||||
|
|
||||||
|
if (this.multiLine) {
|
||||||
|
this.textComponent = new CustomJTextArea();
|
||||||
|
// this.scrollPane = new CustomJScrollPane(this.textComponent);
|
||||||
|
|
||||||
|
this.textComponent.addMouseWheelListener(new MouseWheelListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseWheelMoved(final MouseWheelEvent e) {
|
||||||
|
final int displayRows = countMatches(G2DTextfieldControlImpl2.this.lastCaption, '\n');
|
||||||
|
if (displayRows == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.getWheelRotation() == -1) {
|
||||||
|
G2DTextfieldControlImpl2.this.scrollbar.setValue(Math.min(1.0, Math.max(0.0, G2DTextfieldControlImpl2.this.scrollbar.getValue() + (1.0 / (double) displayRows))));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
G2DTextfieldControlImpl2.this.scrollbar.setValue(Math.min(1.0, Math.max(0.0, G2DTextfieldControlImpl2.this.scrollbar.getValue() - (1.0 / (double) displayRows))));
|
||||||
|
}
|
||||||
|
|
||||||
|
G2DTextfieldControlImpl2.this.onScroll(G2DTextfieldControlImpl2.this.scrollbar.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.textComponent = new CustomJTextField();
|
||||||
|
((JTextField) this.textComponent).addActionListener(event -> {
|
||||||
|
// Simulate pressing enter
|
||||||
|
this.rendererData.getEventConsumer().accept(new UiControlEvent(this.id, (this.lastText == null ? "" : this.lastText) + "\n"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.textComponent.setName(this.id);
|
||||||
|
this.initControl();
|
||||||
|
|
||||||
|
if (this.multiLine) {
|
||||||
|
// ((G2DRenderer) this.RendererData.getRenderer()).getMainFrame().add(this.scrollPane);
|
||||||
|
((G2DRenderer) this.rendererData.getRenderer()).getMainFrame().add(this.textComponent);
|
||||||
|
|
||||||
|
this.scrollbar.setColor(RgbaColor.LIGHT_GRAY);
|
||||||
|
this.scrollbar.setHorizontal(false);
|
||||||
|
this.scrollbar.setValue(1.0);
|
||||||
|
this.scrollbar.setValue2(1.0);
|
||||||
|
this.scrollbar.build();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
((G2DRenderer) this.rendererData.getRenderer()).getMainFrame().add(this.textComponent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void performAction() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render() {
|
||||||
|
final String curComponentText = this.textComponent.getText();
|
||||||
|
if (!curComponentText.equals(this.lastText) && !(this.lastText == null || this.lastText.isEmpty() || curComponentText == null || curComponentText.isEmpty())) {
|
||||||
|
this.lastText = curComponentText;
|
||||||
|
this.rendererData.getEventConsumer().accept(new UiControlEvent(this.id, this.lastText));
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean componentDirty;
|
||||||
|
if (this.multiLine) {
|
||||||
|
componentDirty = ((CustomJTextArea) this.textComponent).isDirty();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
componentDirty = ((CustomJTextField) this.textComponent).isDirty();
|
||||||
|
}
|
||||||
|
if (this.modified || componentDirty || this.scrollPositionModified) {
|
||||||
|
this.rebuildBufferedImage();
|
||||||
|
this.bufferedImageGraphics.setClip(0, 0, this.bufferedImage.getWidth(), this.bufferedImage.getHeight());
|
||||||
|
if (this.multiLine) {
|
||||||
|
((CustomJTextArea) this.textComponent).setDrawGraphics(this.bufferedImageGraphics);
|
||||||
|
// this.scrollPane.setDrawGraphics(this.bufferedImageGraphics);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
((CustomJTextField) this.textComponent).setDrawGraphics(this.bufferedImageGraphics);
|
||||||
|
}
|
||||||
|
this.initControl();
|
||||||
|
if (this.multiLine) {
|
||||||
|
((CustomJTextArea) this.textComponent).setDirty(false);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
((CustomJTextField) this.textComponent).setDirty(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.multiLine) {
|
||||||
|
// this.safeInvoke(() -> this.scrollPane.paint(this.bufferedImageGraphics));
|
||||||
|
this.safeInvoke(() -> this.textComponent.paint(this.bufferedImageGraphics));
|
||||||
|
this.scrollbar.render();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.safeInvoke(() -> this.textComponent.paint(this.bufferedImageGraphics));
|
||||||
|
}
|
||||||
|
|
||||||
|
super.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
if (this.textComponent.isFocusOwner()) {
|
||||||
|
((G2DRenderer) this.rendererData.getRenderer()).getMainFrame().requestFocus();
|
||||||
|
}
|
||||||
|
this.safeInvoke(() -> this.textComponent.setVisible(false));
|
||||||
|
this.safeInvoke(() -> this.textComponent.invalidate());
|
||||||
|
if (this.multiLine) {
|
||||||
|
// this.safeInvoke(() -> this.scrollPane.invalidate());
|
||||||
|
// ((G2DRenderer) this.RendererData.getRenderer()).getMainFrame().remove(this.scrollPane);
|
||||||
|
((G2DRenderer) this.rendererData.getRenderer()).getMainFrame().remove(this.textComponent);
|
||||||
|
this.scrollbar.close();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
((G2DRenderer) this.rendererData.getRenderer()).getMainFrame().remove(this.textComponent);
|
||||||
|
}
|
||||||
|
|
||||||
|
super.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initControl() {
|
||||||
|
this.safeInvoke(() -> this.textComponent.setBackground(this.backgroundColor != null ? this.backgroundColor.toAwtColor() : ComponentRenderingSupport.COLOR_COMPONENT_BGR));
|
||||||
|
this.safeInvoke(() -> this.textComponent.setForeground(Color.WHITE));
|
||||||
|
this.safeInvoke(() -> this.textComponent.setFont(G2DDrawFont.getFont(this.scaleFactor, this.fontSize)));
|
||||||
|
if (this.multiLine) {
|
||||||
|
// this.safeInvoke(() -> this.scrollPane.setLocation(this.x, this.y));
|
||||||
|
// this.safeInvoke(() -> this.scrollPane.setSize(this.width, this.height));
|
||||||
|
this.safeInvoke(() -> this.textComponent.setLocation(this.x, this.y));
|
||||||
|
this.safeInvoke(() -> this.textComponent.setSize(this.width - 30, this.height));
|
||||||
|
this.safeInvoke(() -> this.textComponent.setEditable(false));
|
||||||
|
// this.safeInvoke(() -> this.textComponent.setFocusable(false));
|
||||||
|
this.safeInvoke(() -> ((CustomJTextArea) this.textComponent).setLineWrap(true));
|
||||||
|
this.safeInvoke(() -> ((CustomJTextArea) this.textComponent).setWrapStyleWord(true));
|
||||||
|
|
||||||
|
this.scrollbar.setX(this.x + this.width - 30);
|
||||||
|
this.scrollbar.setY(this.y);
|
||||||
|
this.scrollbar.setWidth(30);
|
||||||
|
this.scrollbar.setHeight(this.height);
|
||||||
|
this.scrollbar.setScaleFactor(this.scaleFactor);
|
||||||
|
this.scrollbar.setEnabled(this.enabled);
|
||||||
|
this.scrollbar.setVisible(this.visible);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.safeInvoke(() -> this.textComponent.setLocation(this.x, this.y));
|
||||||
|
this.safeInvoke(() -> this.textComponent.setSize(this.width, this.height));
|
||||||
|
this.safeInvoke(() -> this.textComponent.setEditable(this.enabled));
|
||||||
|
}
|
||||||
|
this.safeInvoke(() -> this.textComponent.setVisible(this.visible));
|
||||||
|
this.safeInvoke(() -> this.textComponent.setEditable(!this.readonly));
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (this.multiLine) {
|
||||||
|
if (this.scrollPositionModified || this.caption != null) {
|
||||||
|
final int displayRows = countMatches(this.lastCaption, '\n');
|
||||||
|
final int skipRows = (int) ((double) this.scrollPositionPerc * (double) displayRows);
|
||||||
|
int offset = 0;
|
||||||
|
for (int i = 0; i < skipRows; i++) {
|
||||||
|
offset = this.lastCaption.indexOf('\n', offset);
|
||||||
|
if (offset == -1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
offset++;
|
||||||
|
}
|
||||||
|
String displayText;
|
||||||
|
if (offset > -1) {
|
||||||
|
displayText = this.lastCaption.substring(offset);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
displayText = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.lastText.equals(displayText)) {
|
||||||
|
this.safeInvoke(() -> this.textComponent.setText(displayText));
|
||||||
|
// this.safeInvoke(() -> this.textComponent.setCaretPosition(this.textComponent.getDocument().getLength()));
|
||||||
|
|
||||||
|
this.lastText = displayText;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.scrollPositionModified = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (this.caption != null && !this.textComponent.getText().equals(this.caption)) {
|
||||||
|
final int pos = this.textComponent.getCaretPosition();
|
||||||
|
this.safeInvoke(() -> this.textComponent.setText(this.caption));
|
||||||
|
if (pos < this.textComponent.getText().length()) {
|
||||||
|
this.safeInvoke(() -> this.textComponent.setCaretPosition(pos));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lastText = this.caption;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (final Exception exc) {
|
||||||
|
this.modified = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onScroll(final double value) {
|
||||||
|
this.scrollPositionPerc = 1.0 - value;
|
||||||
|
this.scrollPositionModified = true;
|
||||||
|
this.scrollbar.setValue2(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int countMatches(final String str, final char ch) {
|
||||||
|
if (str == null || str.isEmpty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int count = 0;
|
||||||
|
for (int i = 0; i < str.length(); i++) {
|
||||||
|
if (str.charAt(i) == ch) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void safeInvoke(final Runnable runnable) {
|
||||||
|
try {
|
||||||
|
runnable.run();
|
||||||
|
}
|
||||||
|
catch (final Exception exc) {
|
||||||
|
LOGGER.warn(exc.getMessage(), exc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public G2DBaseControlImpl setScaleFactor(final double scaleFactor) {
|
||||||
|
this.scrollbar.setScaleFactor(scaleFactor);
|
||||||
|
return super.setScaleFactor(scaleFactor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BaseControl setControlGroup(final String controlGroup) {
|
||||||
|
this.scrollbar.setControlGroup(controlGroup);
|
||||||
|
return super.setControlGroup(controlGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BaseControl setRendererData(final RendererData RendererData) {
|
||||||
|
this.scrollbar.setRendererData(RendererData);
|
||||||
|
return super.setRendererData(RendererData);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public G2DBaseControlImpl setMainFrameGraphics(final Graphics2D graphics) {
|
||||||
|
this.scrollbar.setMainFrameGraphics(graphics);
|
||||||
|
return super.setMainFrameGraphics(graphics);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BaseControl setId(final String id) {
|
||||||
|
this.scrollbar.setId(id + "_scroll");
|
||||||
|
return super.setId(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package de.extio.game_engine.renderer.g2d.control.impl;
|
||||||
|
|
||||||
|
import de.extio.game_engine.renderer.g2d.control.components.CustomButton;
|
||||||
|
|
||||||
|
public class G2DToggleButtonControlImpl extends G2DAbstractToggleButtonControlImpl {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void createControl() {
|
||||||
|
this.control = new CustomButton(true, event -> this.performAction());
|
||||||
|
this.control.setName(this.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package de.extio.game_engine.renderer.g2d.control.impl;
|
||||||
|
|
||||||
|
import de.extio.game_engine.renderer.model.bo.ControlRenderingBo.TooltipControl;
|
||||||
|
import de.extio.game_engine.spatial2.model.ImmutableCoordI2;
|
||||||
|
|
||||||
|
public class G2DTooltipControl extends G2DBaseControlImpl implements TooltipControl {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void performAction() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render() {
|
||||||
|
this.tooltipMousePosition = ImmutableCoordI2.zero();
|
||||||
|
super.render();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
package de.extio.game_engine.renderer.g2d.control.impl;
|
||||||
|
|
||||||
|
import de.extio.game_engine.renderer.g2d.control.components.CustomWindowCloseButton;
|
||||||
|
import de.extio.game_engine.renderer.model.bo.ControlRenderingBo.WindowCloseButtonControl;
|
||||||
|
|
||||||
|
public class G2DWindowCloseButtonControlImpl extends G2DButtonControlImpl implements WindowCloseButtonControl {
|
||||||
|
|
||||||
|
private int translations;
|
||||||
|
|
||||||
|
private boolean thickBorder;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void createControl() {
|
||||||
|
this.control = new CustomWindowCloseButton(false, event -> this.performAction());
|
||||||
|
this.control.setName(this.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void initControl() {
|
||||||
|
this.translate(super::initControl);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render() {
|
||||||
|
this.translate(super::render);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void rebuildBufferedImage() {
|
||||||
|
this.translate(super::rebuildBufferedImage);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void translate(final Runnable run) {
|
||||||
|
if (this.translations > 0) {
|
||||||
|
run.run();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.translations++;
|
||||||
|
final int x = this.x;
|
||||||
|
final int y = this.y;
|
||||||
|
final int w = this.width;
|
||||||
|
final int h = this.height;
|
||||||
|
try {
|
||||||
|
final int sx = (int) this.x;
|
||||||
|
final int sy = (int) this.y;
|
||||||
|
final int sw = (int) this.width;
|
||||||
|
final int strength = Math.max(2, (int) (3 * this.scaleFactor));
|
||||||
|
|
||||||
|
this.x = sx + sw - strength * 9;
|
||||||
|
this.y = sy + strength * 2;
|
||||||
|
this.width = strength * 7;
|
||||||
|
this.height = strength * 7;
|
||||||
|
|
||||||
|
run.run();
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.width = w;
|
||||||
|
this.height = h;
|
||||||
|
this.translations--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isThickBorder() {
|
||||||
|
return this.thickBorder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setThickBorder(final boolean thickBorder) {
|
||||||
|
this.thickBorder = thickBorder;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,285 @@
|
|||||||
|
package de.extio.game_engine.renderer.model;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import de.extio.game_engine.renderer.RendererData;
|
||||||
|
import de.extio.game_engine.spatial2.model.HasPosition2;
|
||||||
|
|
||||||
|
public abstract class AbstractRenderingBo implements RenderingBo {
|
||||||
|
|
||||||
|
protected final static Logger LOGGER = LoggerFactory.getLogger(AbstractRenderingBo.class);
|
||||||
|
|
||||||
|
protected RendererData rendererData;
|
||||||
|
|
||||||
|
protected RgbaColor color;
|
||||||
|
|
||||||
|
protected int x;
|
||||||
|
|
||||||
|
protected int y;
|
||||||
|
|
||||||
|
protected int localX;
|
||||||
|
|
||||||
|
protected int localY;
|
||||||
|
|
||||||
|
protected int width;
|
||||||
|
|
||||||
|
protected int height;
|
||||||
|
|
||||||
|
protected RenderingBoLayer layer;
|
||||||
|
|
||||||
|
private final RenderingBoLayer defaultLayer;
|
||||||
|
|
||||||
|
public AbstractRenderingBo(final RenderingBoLayer layer) {
|
||||||
|
this.layer = layer;
|
||||||
|
this.defaultLayer = layer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RenderingBo withPositionRelative(final int x, final int y) {
|
||||||
|
this.localX = x;
|
||||||
|
this.localY = y;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RenderingBo withPositionRelative(final HasPosition2 position) {
|
||||||
|
if (position.getPosition() != null) {
|
||||||
|
this.localX = position.getPosition().getX();
|
||||||
|
this.localY = position.getPosition().getY();
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RenderingBo withPositionAbsoluteAnchorTopLeft(final int x, final int y) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RenderingBo withPositionAbsoluteAnchorTopLeft(final HasPosition2 position) {
|
||||||
|
if (position.getPosition() != null) {
|
||||||
|
this.withPositionAbsoluteAnchorTopLeft(position.getPosition().getX(), position.getPosition().getY());
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RenderingBo withPositionAbsoluteAnchorTopRight(final int x, final int y) {
|
||||||
|
this.x = this.rendererData.getRendererControl().getEffectiveViewportDimension().getX() - x;
|
||||||
|
this.y = y;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RenderingBo withPositionAbsoluteAnchorTopRight(final HasPosition2 position) {
|
||||||
|
if (position.getPosition() != null) {
|
||||||
|
this.withPositionAbsoluteAnchorTopRight(position.getPosition().getX(), position.getPosition().getY());
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RenderingBo withPositionAbsoluteAnchorBottomLeft(final int x, final int y) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = this.rendererData.getRendererControl().getEffectiveViewportDimension().getY() - y;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RenderingBo withPositionAbsoluteAnchorBottomLeft(final HasPosition2 position) {
|
||||||
|
if (position.getPosition() != null) {
|
||||||
|
this.withPositionAbsoluteAnchorBottomLeft(position.getPosition().getX(), position.getPosition().getY());
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RenderingBo withPositionAbsoluteAnchorBottomRight(final int x, final int y) {
|
||||||
|
this.x = this.rendererData.getRendererControl().getEffectiveViewportDimension().getX() - x;
|
||||||
|
this.y = this.rendererData.getRendererControl().getEffectiveViewportDimension().getY() - y;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RenderingBo withPositionAbsoluteAnchorBottomRight(final HasPosition2 position) {
|
||||||
|
if (position.getPosition() != null) {
|
||||||
|
this.withPositionAbsoluteAnchorBottomRight(position.getPosition().getX(), position.getPosition().getY());
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RenderingBo withPositionIncrementalAbsolute(final int x, final int y) {
|
||||||
|
this.x += x;
|
||||||
|
this.y += y;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RenderingBo withPositionIncrementalAbsolute(final HasPosition2 position) {
|
||||||
|
if (position.getPosition() != null) {
|
||||||
|
this.withPositionIncrementalAbsolute(position.getPosition().getX(), position.getPosition().getY());
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RenderingBo withPositionIncrementalPercentual(final double x, final double y) {
|
||||||
|
this.x += (int) (this.rendererData.getRendererControl().getEffectiveViewportDimension().getX() * Math.min(1.0, Math.max(0.0, x)));
|
||||||
|
this.y += (int) (this.rendererData.getRendererControl().getEffectiveViewportDimension().getY() * Math.min(1.0, Math.max(0.0, y)));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param x Range 0.0 (left) to 1.0 (right)
|
||||||
|
* @param y Range 0.0 (top) to 1.0 (bottom)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public RenderingBo withPositionPercentual(final double x, final double y) {
|
||||||
|
this.x = (int) (this.rendererData.getRendererControl().getEffectiveViewportDimension().getX() * Math.min(1.0, Math.max(0.0, x)));
|
||||||
|
this.y = (int) (this.rendererData.getRendererControl().getEffectiveViewportDimension().getY() * Math.min(1.0, Math.max(0.0, y)));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RenderingBoHasDimension withDimensionAbsolute(final HasPosition2 dim) {
|
||||||
|
return this.withDimensionAbsolute(dim.getPosition().getX(), dim.getPosition().getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
public RenderingBoHasDimension withDimensionAbsolute(final int width, final int height) {
|
||||||
|
if (!(this instanceof RenderingBoHasDimension)) {
|
||||||
|
throw new IllegalArgumentException("RenderingBo is not instanceof RenderingBoHasDimension");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
|
||||||
|
return (RenderingBoHasDimension) this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param width Range 0.0 (left) to 1.0 (right)
|
||||||
|
* @param height Range 0.0 (top) to 1.0 (bottom)
|
||||||
|
*/
|
||||||
|
public RenderingBoHasDimension withDimensionPercentual(final double width, final double height) {
|
||||||
|
if (!(this instanceof RenderingBoHasDimension)) {
|
||||||
|
throw new IllegalArgumentException("RenderingBo is not instanceof RenderingBoHasDimension");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.width = (int) (this.rendererData.getRendererControl().getEffectiveViewportDimension().getX() * Math.min(1.0, Math.max(0.0, width)));
|
||||||
|
this.height = (int) (this.rendererData.getRendererControl().getEffectiveViewportDimension().getY() * Math.min(1.0, Math.max(0.0, height)));
|
||||||
|
|
||||||
|
return (RenderingBoHasDimension) this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RenderingBoHasDimension withDimensionIncrementalAbsolute(final HasPosition2 dim) {
|
||||||
|
return this.withDimensionIncrementalAbsolute(dim.getPosition().getX(), dim.getPosition().getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
public RenderingBoHasDimension withDimensionIncrementalAbsolute(final int width, final int height) {
|
||||||
|
if (!(this instanceof RenderingBoHasDimension)) {
|
||||||
|
throw new IllegalArgumentException("RenderingBo is not instanceof RenderingBoHasDimension");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.width += width;
|
||||||
|
this.height += height;
|
||||||
|
|
||||||
|
return (RenderingBoHasDimension) this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RenderingBoHasDimension withDimensionIncrementalPercentual(final double width, final double height) {
|
||||||
|
if (!(this instanceof RenderingBoHasDimension)) {
|
||||||
|
throw new IllegalArgumentException("RenderingBo is not instanceof RenderingBoHasDimension");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.width += (int) (this.rendererData.getRendererControl().getEffectiveViewportDimension().getX() * Math.min(1.0, Math.max(0.0, width)));
|
||||||
|
this.height += (int) (this.rendererData.getRendererControl().getEffectiveViewportDimension().getY() * Math.min(1.0, Math.max(0.0, height)));
|
||||||
|
|
||||||
|
return (RenderingBoHasDimension) this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws Exception {
|
||||||
|
this.color = null;
|
||||||
|
this.x = 0;
|
||||||
|
this.y = 0;
|
||||||
|
this.localX = 0;
|
||||||
|
this.localY = 0;
|
||||||
|
this.width = 0;
|
||||||
|
this.height = 0;
|
||||||
|
this.layer = this.defaultLayer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRendererData(final RendererData RendererData) {
|
||||||
|
this.rendererData = (RendererData) RendererData;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RenderingBo setColor(final RgbaColor color) {
|
||||||
|
this.color = color;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected RenderingBo setX(final int x) {
|
||||||
|
this.x = x;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getX() {
|
||||||
|
return this.x;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected RenderingBo setY(final int y) {
|
||||||
|
this.y = y;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getY() {
|
||||||
|
return this.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLocalX() {
|
||||||
|
return this.localX;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getLocalY() {
|
||||||
|
return this.localY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RenderingBoLayer getLayer() {
|
||||||
|
return this.layer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RenderingBo setLayer(final RenderingBoLayer layer) {
|
||||||
|
if (layer != null) {
|
||||||
|
this.layer = layer;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getWidth() {
|
||||||
|
return this.width;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWidth(final int width) {
|
||||||
|
this.width = width;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getHeight() {
|
||||||
|
return this.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHeight(final int height) {
|
||||||
|
this.height = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||||
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||||
|
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package de.extio.game_engine.renderer.model;
|
||||||
|
|
||||||
|
public enum DrawEffectRenderingBoEffects {
|
||||||
|
|
||||||
|
LINE,
|
||||||
|
RECT,
|
||||||
|
RECT_FILLED,
|
||||||
|
CIRCLE,
|
||||||
|
CIRCLE_FILLED,
|
||||||
|
CIRCLE_CENTERED,
|
||||||
|
CIRCLE_CENTERED_FILLED,
|
||||||
|
ARROW,
|
||||||
|
CONE,
|
||||||
|
DECORATIVE_BORDER,
|
||||||
|
DECORATIVE_BORDER_FILLED,
|
||||||
|
TEXT
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||||
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||||
|
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package de.extio.game_engine.renderer.model;
|
||||||
|
|
||||||
|
public enum DrawFontRenderingBoTextAlignment {
|
||||||
|
|
||||||
|
LEFT,
|
||||||
|
CENTER,
|
||||||
|
RIGHT
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||||
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||||
|
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package de.extio.game_engine.renderer.model;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
|
||||||
|
public final class ImmutableRgbaColor extends RgbaColor {
|
||||||
|
|
||||||
|
private transient Color awtColor;
|
||||||
|
|
||||||
|
ImmutableRgbaColor() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImmutableRgbaColor(final RgbaColor color) {
|
||||||
|
this.r = color.getR();
|
||||||
|
this.g = color.getG();
|
||||||
|
this.b = color.getB();
|
||||||
|
this.a = color.getA();
|
||||||
|
this.awtColor = super.toAwtColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImmutableRgbaColor(final int r, final int g, final int b) {
|
||||||
|
this.r = r;
|
||||||
|
this.g = g;
|
||||||
|
this.b = b;
|
||||||
|
this.a = 255;
|
||||||
|
this.awtColor = super.toAwtColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImmutableRgbaColor(final int r, final int g, final int b, final int a) {
|
||||||
|
this.r = r;
|
||||||
|
this.g = g;
|
||||||
|
this.b = b;
|
||||||
|
this.a = a;
|
||||||
|
this.awtColor = super.toAwtColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void xor(final RgbaColor other) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void or(final RgbaColor other) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void and(final RgbaColor other) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void screen(final RgbaColor other) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setA(final int a) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setB(final int b) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setG(final int g) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setR(final int r) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Color toAwtColor() {
|
||||||
|
if (this.awtColor != null) {
|
||||||
|
return this.awtColor;
|
||||||
|
}
|
||||||
|
return super.toAwtColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||||
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||||
|
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package de.extio.game_engine.renderer.model;
|
||||||
|
|
||||||
|
import de.extio.game_engine.renderer.RendererData;
|
||||||
|
import de.extio.game_engine.spatial2.model.HasPosition2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RenderingBo is the basic contract for all business objects (bo) implementing building blocks for rendering.
|
||||||
|
* For rendering stuff, usually you are acquiring an instance of an implementation of this interface via RenderingBoPool, set all properties and add it to <i>List<RenderingBo> renderingBo</i> which is passed to various callback methods that are part of the rendering pipeline.
|
||||||
|
* It is recommended to view my technical presentation on youtube about the game to learn more about how rendering works.
|
||||||
|
*/
|
||||||
|
public interface RenderingBo extends AutoCloseable {
|
||||||
|
|
||||||
|
void setRendererData(RendererData RendererData);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a color you want to use for rendering. It depends on the rendering bo how this information is treated.
|
||||||
|
*/
|
||||||
|
RenderingBo setColor(RgbaColor color);
|
||||||
|
|
||||||
|
int getX();
|
||||||
|
|
||||||
|
int getY();
|
||||||
|
|
||||||
|
int getLocalX();
|
||||||
|
|
||||||
|
int getLocalY();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the layer to render this bo on. Business objects are rendered layered on top of each other in the natural order of RenderingBoLayer enum fields. Objects on the same layer are rendered in the order of being added to <i>List<RenderingBo> renderingBo</i>.
|
||||||
|
*/
|
||||||
|
RenderingBo setLayer(RenderingBoLayer layer);
|
||||||
|
|
||||||
|
RenderingBoLayer getLayer();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Positions this bo with an absolute position on the viewport. x0 y0 coordinates are at the top left. Call RendererControl#getEffectiveViewportDimension to get the absolute dimension of the viewport.
|
||||||
|
*/
|
||||||
|
RenderingBo withPositionAbsoluteAnchorTopLeft(int x, int y);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Positions this bo with an absolute position on the viewport. x0 y0 coordinates are at the top left. Call RendererControl#getEffectiveViewportDimension to get the absolute dimension of the viewport.
|
||||||
|
*/
|
||||||
|
RenderingBo withPositionAbsoluteAnchorTopLeft(HasPosition2 position);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Positions this bo with an absolute position on the viewport. x0 y0 coordinates are at the top right. Call RendererControl#getEffectiveViewportDimension to get the absolute dimension of the viewport.
|
||||||
|
*/
|
||||||
|
RenderingBo withPositionAbsoluteAnchorTopRight(int x, int y);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Positions this bo with an absolute position on the viewport. x0 y0 coordinates are at the top right. Call RendererControl#getEffectiveViewportDimension to get the absolute dimension of the viewport.
|
||||||
|
*/
|
||||||
|
RenderingBo withPositionAbsoluteAnchorTopRight(HasPosition2 position);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Positions this bo with an absolute position on the viewport. x0 y0 coordinates are at the bottom left. Call RendererControl#getEffectiveViewportDimension to get the absolute dimension of the viewport.
|
||||||
|
*/
|
||||||
|
RenderingBo withPositionAbsoluteAnchorBottomLeft(int x, int y);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Positions this bo with an absolute position on the viewport. x0 y0 coordinates are at the bottom left. Call RendererControl#getEffectiveViewportDimension to get the absolute dimension of the viewport.
|
||||||
|
*/
|
||||||
|
RenderingBo withPositionAbsoluteAnchorBottomLeft(HasPosition2 position);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Positions this bo with an absolute position on the viewport. x0 y0 coordinates are at the bottom right. Call RendererControl#getEffectiveViewportDimension to get the absolute dimension of the viewport.
|
||||||
|
*/
|
||||||
|
RenderingBo withPositionAbsoluteAnchorBottomRight(int x, int y);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Positions this bo with an absolute position on the viewport. x0 y0 coordinates are at the bottom right. Call RendererControl#getEffectiveViewportDimension to get the absolute dimension of the viewport.
|
||||||
|
*/
|
||||||
|
RenderingBo withPositionAbsoluteAnchorBottomRight(HasPosition2 position);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Positions this bo with a percentual position on the viewport.
|
||||||
|
* @param x Range 0.0 (left) to 1.0 (right)
|
||||||
|
* @param y Range 0.0 (top) to 1.0 (bottom)
|
||||||
|
*/
|
||||||
|
RenderingBo withPositionPercentual(double x, double y);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Positions this bo with an incremental position relative to the position the bo is already at. Call RendererControl#getEffectiveViewportDimension to get the absolute dimension of the viewport.
|
||||||
|
*/
|
||||||
|
RenderingBo withPositionIncrementalAbsolute(int x, int y);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Positions this bo with an incremental position relative to the position the bo is already at. Call RendererControl#getEffectiveViewportDimension to get the absolute dimension of the viewport.
|
||||||
|
*/
|
||||||
|
RenderingBo withPositionIncrementalAbsolute(HasPosition2 position);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Positions this bo with a percenutal position relative to the position the bo is already at.
|
||||||
|
*/
|
||||||
|
RenderingBo withPositionIncrementalPercentual(double x, double y);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Positions this bo relative to it's parent. This can be for example the game world for positioning tiles or a composite entity. Unit is usually not pixels but the position in the tile raster.
|
||||||
|
*/
|
||||||
|
RenderingBo withPositionRelative(int x, int y);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Positions this bo relative to it's parent. This can be for example game world for positioning tiles or a composite entity. Unit is usually not pixels but the position in the tile raster.
|
||||||
|
*/
|
||||||
|
RenderingBo withPositionRelative(HasPosition2 position);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||||
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||||
|
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package de.extio.game_engine.renderer.model;
|
||||||
|
|
||||||
|
import de.extio.game_engine.spatial2.model.HasPosition2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rendering business objects implementing this interface have a dimension
|
||||||
|
*/
|
||||||
|
public interface RenderingBoHasDimension extends RenderingBo {
|
||||||
|
|
||||||
|
public int getWidth();
|
||||||
|
|
||||||
|
public int getHeight();
|
||||||
|
|
||||||
|
RenderingBoHasDimension withDimensionAbsolute(HasPosition2 dim);
|
||||||
|
|
||||||
|
RenderingBoHasDimension withDimensionAbsolute(int width, int height);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param width Range 0.0 (No width) to 1.0 (Viewport width)
|
||||||
|
* @param height Range 0.0 (No height) to 1.0 (Viewport height)
|
||||||
|
*/
|
||||||
|
RenderingBoHasDimension withDimensionPercentual(double width, double height);
|
||||||
|
|
||||||
|
RenderingBoHasDimension withDimensionIncrementalAbsolute(HasPosition2 dim);
|
||||||
|
|
||||||
|
RenderingBoHasDimension withDimensionIncrementalAbsolute(int width, int height);
|
||||||
|
|
||||||
|
RenderingBoHasDimension withDimensionIncrementalPercentual(double width, double height);
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||||
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||||
|
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package de.extio.game_engine.renderer.model;
|
||||||
|
|
||||||
|
public enum RenderingBoLayer {
|
||||||
|
|
||||||
|
BACKGROUND0,
|
||||||
|
BACKGROUND1,
|
||||||
|
UI0_BGR,
|
||||||
|
UI0_0,
|
||||||
|
UI0_1,
|
||||||
|
UI0_2,
|
||||||
|
UI1_BGR,
|
||||||
|
UI1_0,
|
||||||
|
UI1_1,
|
||||||
|
UI2_BGR,
|
||||||
|
UI2_0,
|
||||||
|
UI2_1,
|
||||||
|
TOP;
|
||||||
|
|
||||||
|
}
|
||||||
225
src/main/java/de/extio/game_engine/renderer/model/RgbaColor.java
Normal file
225
src/main/java/de/extio/game_engine/renderer/model/RgbaColor.java
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||||
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||||
|
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package de.extio.game_engine.renderer.model;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
|
||||||
|
public class RgbaColor {
|
||||||
|
|
||||||
|
public final static RgbaColor WHITE = new ImmutableRgbaColor(255, 255, 255, 255);
|
||||||
|
|
||||||
|
public final static RgbaColor LIGHT_GRAY = new ImmutableRgbaColor(192, 192, 192, 255);
|
||||||
|
|
||||||
|
public final static RgbaColor GRAY = new ImmutableRgbaColor(128, 128, 128, 255);
|
||||||
|
|
||||||
|
public final static RgbaColor DARK_GRAY = new ImmutableRgbaColor(64, 64, 64, 255);
|
||||||
|
|
||||||
|
public final static RgbaColor DARK_GRAY2 = new ImmutableRgbaColor(32, 32, 32, 255);
|
||||||
|
|
||||||
|
public final static RgbaColor BLACK = new ImmutableRgbaColor(0, 0, 0, 255);
|
||||||
|
|
||||||
|
public final static RgbaColor BLACK_DARKEN = new ImmutableRgbaColor(0, 0, 0, 100);
|
||||||
|
|
||||||
|
public final static RgbaColor LIGHT_RED = new ImmutableRgbaColor(255, 128, 128, 255);
|
||||||
|
|
||||||
|
public final static RgbaColor RED = new ImmutableRgbaColor(255, 0, 0, 255);
|
||||||
|
|
||||||
|
public final static RgbaColor DARK_RED = new ImmutableRgbaColor(128, 0, 0, 255);
|
||||||
|
|
||||||
|
public final static RgbaColor DARK_RED2 = new ImmutableRgbaColor(64, 0, 0, 255);
|
||||||
|
|
||||||
|
public final static RgbaColor PINK = new ImmutableRgbaColor(255, 175, 175, 255);
|
||||||
|
|
||||||
|
public final static RgbaColor DARK_PINK = new ImmutableRgbaColor(128, 88, 88, 255);
|
||||||
|
|
||||||
|
public final static RgbaColor DARK_PINK2 = new ImmutableRgbaColor(64, 44, 44, 255);
|
||||||
|
|
||||||
|
public final static RgbaColor LIGHT_YELLOW = new ImmutableRgbaColor(235, 255, 151, 255);
|
||||||
|
|
||||||
|
public final static RgbaColor YELLOW = new ImmutableRgbaColor(214, 255, 43, 255);
|
||||||
|
|
||||||
|
public final static RgbaColor DARK_YELLOW = new ImmutableRgbaColor(160, 192, 33, 255);
|
||||||
|
|
||||||
|
public final static RgbaColor DARK_YELLOW2 = new ImmutableRgbaColor(64, 79, 0, 255);
|
||||||
|
|
||||||
|
public final static RgbaColor LIGHT_ORANGE = new ImmutableRgbaColor(255, 228, 128, 255);
|
||||||
|
|
||||||
|
public final static RgbaColor ORANGE = new ImmutableRgbaColor(255, 200, 0, 255);
|
||||||
|
|
||||||
|
public final static RgbaColor DARK_ORANGE = new ImmutableRgbaColor(128, 100, 0, 255);
|
||||||
|
|
||||||
|
public final static RgbaColor DARK_ORANGE2 = new ImmutableRgbaColor(64, 50, 0, 255);
|
||||||
|
|
||||||
|
public final static RgbaColor LIGHT_GREEN = new ImmutableRgbaColor(128, 255, 128, 255);
|
||||||
|
|
||||||
|
public final static RgbaColor GREEN = new ImmutableRgbaColor(0, 255, 0, 255);
|
||||||
|
|
||||||
|
public final static RgbaColor DARK_GREEN = new ImmutableRgbaColor(0, 128, 0, 255);
|
||||||
|
|
||||||
|
public final static RgbaColor DARK_GREEN2 = new ImmutableRgbaColor(0, 64, 0, 255);
|
||||||
|
|
||||||
|
public final static RgbaColor LIGHT_BLUE = new ImmutableRgbaColor(128, 192, 255, 255);
|
||||||
|
|
||||||
|
public final static RgbaColor BLUE = new ImmutableRgbaColor(0, 0, 255, 255);
|
||||||
|
|
||||||
|
public final static RgbaColor DARK_BLUE = new ImmutableRgbaColor(0, 0, 128, 255);
|
||||||
|
|
||||||
|
public final static RgbaColor DARK_BLUE2 = new ImmutableRgbaColor(0, 0, 64, 255);
|
||||||
|
|
||||||
|
protected int r;
|
||||||
|
|
||||||
|
protected int g;
|
||||||
|
|
||||||
|
protected int b;
|
||||||
|
|
||||||
|
protected int a;
|
||||||
|
|
||||||
|
RgbaColor() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public RgbaColor(final int r, final int g, final int b) {
|
||||||
|
this.r = r;
|
||||||
|
this.g = g;
|
||||||
|
this.b = b;
|
||||||
|
this.a = 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RgbaColor(final int r, final int g, final int b, final int a) {
|
||||||
|
this.r = r;
|
||||||
|
this.g = g;
|
||||||
|
this.b = b;
|
||||||
|
this.a = a;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RgbaColor(final Color color) {
|
||||||
|
this.r = color.getRed();
|
||||||
|
this.g = color.getGreen();
|
||||||
|
this.b = color.getBlue();
|
||||||
|
this.a = color.getAlpha();
|
||||||
|
}
|
||||||
|
|
||||||
|
public RgbaColor(final RgbaColor color) {
|
||||||
|
this.r = color.getR();
|
||||||
|
this.g = color.getG();
|
||||||
|
this.b = color.getB();
|
||||||
|
this.a = color.getA();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Color toAwtColor() {
|
||||||
|
return new Color(this.r, this.g, this.b, this.a);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void xor(final RgbaColor other) {
|
||||||
|
this.r ^= other.r;
|
||||||
|
this.g ^= other.g;
|
||||||
|
this.b ^= other.b;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void or(final RgbaColor other) {
|
||||||
|
this.r |= other.r;
|
||||||
|
this.g |= other.g;
|
||||||
|
this.b |= other.b;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void and(final RgbaColor other) {
|
||||||
|
this.r &= other.r;
|
||||||
|
this.g &= other.g;
|
||||||
|
this.b &= other.b;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void screen(final RgbaColor other) {
|
||||||
|
this.r = (int) ((1.0 - (1.0 - (this.r / 255.0)) * (1.0 - (other.r / 255.0))) * 255.0);
|
||||||
|
this.g = (int) ((1.0 - (1.0 - (this.g / 255.0)) * (1.0 - (other.g / 255.0))) * 255.0);
|
||||||
|
this.b = (int) ((1.0 - (1.0 - (this.b / 255.0)) * (1.0 - (other.b / 255.0))) * 255.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getR() {
|
||||||
|
return this.r;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setR(final int r) {
|
||||||
|
this.r = r;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getG() {
|
||||||
|
return this.g;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setG(final int g) {
|
||||||
|
this.g = g;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getB() {
|
||||||
|
return this.b;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setB(final int b) {
|
||||||
|
this.b = b;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getA() {
|
||||||
|
return this.a;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setA(final int a) {
|
||||||
|
this.a = a;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
final int prime = 31;
|
||||||
|
int result = 1;
|
||||||
|
result = prime * result + this.a;
|
||||||
|
result = prime * result + this.b;
|
||||||
|
result = prime * result + this.g;
|
||||||
|
result = prime * result + this.r;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if ((obj == null) || (this.getClass() != obj.getClass())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final RgbaColor other = (RgbaColor) obj;
|
||||||
|
if (this.a != other.a) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this.b != other.b) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this.g != other.g) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this.r != other.r) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
final StringBuilder builder = new StringBuilder();
|
||||||
|
builder.append("RgbaColor [r=");
|
||||||
|
builder.append(this.r);
|
||||||
|
builder.append(", g=");
|
||||||
|
builder.append(this.g);
|
||||||
|
builder.append(", b=");
|
||||||
|
builder.append(this.b);
|
||||||
|
builder.append(", a=");
|
||||||
|
builder.append(this.a);
|
||||||
|
builder.append("]");
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,263 @@
|
|||||||
|
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||||
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||||
|
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package de.extio.game_engine.renderer.model.bo;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import de.extio.game_engine.renderer.RendererData;
|
||||||
|
import de.extio.game_engine.renderer.model.DrawFontRenderingBoTextAlignment;
|
||||||
|
import de.extio.game_engine.renderer.model.RenderingBoHasDimension;
|
||||||
|
import de.extio.game_engine.renderer.model.RgbaColor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This bo renders ui controls (buttons, ...)
|
||||||
|
*/
|
||||||
|
public interface ControlRenderingBo extends RenderingBoHasDimension {
|
||||||
|
|
||||||
|
ControlRenderingBo setId(String id);
|
||||||
|
|
||||||
|
String getId();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the type of the control. See class tree of BaseControl
|
||||||
|
*/
|
||||||
|
ControlRenderingBo setType(Class<? extends BaseControl> clazz);
|
||||||
|
|
||||||
|
ControlRenderingBo setCaption(String caption);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional, some controls (e.g. toggle buttons) can build groups
|
||||||
|
*/
|
||||||
|
ControlRenderingBo setControlGroup(String controlGroup);
|
||||||
|
|
||||||
|
ControlRenderingBo setFontSize(int size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom data a specific to the type of the control. Best is to search for code examples in shipped groovy mods to see what can be set
|
||||||
|
*/
|
||||||
|
ControlRenderingBo setCustomData(Object data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom data a specific to the type of the control. Best is to search for code examples in shipped groovy mods to see what can be set
|
||||||
|
*/
|
||||||
|
ControlRenderingBo setCustomData2(Object data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom data a specific to the type of the control. Best is to search for code examples in shipped groovy mods to see what can be set
|
||||||
|
*/
|
||||||
|
ControlRenderingBo setCustomData3(Object data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom data a specific to the type of the control. Best is to search for code examples in shipped groovy mods to see what can be set
|
||||||
|
*/
|
||||||
|
ControlRenderingBo setCustomData4(Object data);
|
||||||
|
|
||||||
|
ControlRenderingBo setVisible(boolean visible);
|
||||||
|
|
||||||
|
ControlRenderingBo setEnabled(boolean enabled);
|
||||||
|
|
||||||
|
ControlRenderingBo setTooltip(String tooltip);
|
||||||
|
|
||||||
|
public static interface BaseControl {
|
||||||
|
|
||||||
|
BaseControl setRendererData(RendererData RendererData);
|
||||||
|
|
||||||
|
BaseControl setInUse(boolean inUse);
|
||||||
|
|
||||||
|
boolean getInUse();
|
||||||
|
|
||||||
|
BaseControl setId(String id);
|
||||||
|
|
||||||
|
BaseControl setCaption(String caption);
|
||||||
|
|
||||||
|
BaseControl setX(int x);
|
||||||
|
|
||||||
|
BaseControl setY(int y);
|
||||||
|
|
||||||
|
BaseControl setWidth(int width);
|
||||||
|
|
||||||
|
BaseControl setHeight(int height);
|
||||||
|
|
||||||
|
BaseControl setFontSize(int size);
|
||||||
|
|
||||||
|
BaseControl setControlGroup(String controlGroup);
|
||||||
|
|
||||||
|
String getControlGroup();
|
||||||
|
|
||||||
|
BaseControl setVisible(boolean visible);
|
||||||
|
|
||||||
|
BaseControl setEnabled(boolean enabled);
|
||||||
|
|
||||||
|
BaseControl setTooltip(String tooltip);
|
||||||
|
|
||||||
|
void build();
|
||||||
|
|
||||||
|
void render();
|
||||||
|
|
||||||
|
void performAction();
|
||||||
|
|
||||||
|
void close();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Label
|
||||||
|
*/
|
||||||
|
public static interface LabelControl extends BaseControl {
|
||||||
|
|
||||||
|
RgbaColor getBackgroundColor();
|
||||||
|
|
||||||
|
void setBackgroundColor(RgbaColor color);
|
||||||
|
|
||||||
|
RgbaColor getForegroundColor();
|
||||||
|
|
||||||
|
void setForegroundColor(RgbaColor color);
|
||||||
|
|
||||||
|
DrawFontRenderingBoTextAlignment getTextAlignment();
|
||||||
|
|
||||||
|
void setTextAlignment(DrawFontRenderingBoTextAlignment alignment);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Button
|
||||||
|
*/
|
||||||
|
public static interface ButtonControl extends BaseControl {
|
||||||
|
|
||||||
|
RgbaColor getBackgroundColor();
|
||||||
|
|
||||||
|
void setBackgroundColor(RgbaColor backgroundColor);
|
||||||
|
|
||||||
|
void setIconResourceName(String iconResourceName);
|
||||||
|
|
||||||
|
String getIconResourceName();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toogle button
|
||||||
|
*/
|
||||||
|
public static interface ToggleButtonControl extends ButtonControl {
|
||||||
|
|
||||||
|
boolean isToggled();
|
||||||
|
|
||||||
|
void setToggled(boolean toggled);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Switch (Modern version of a toggle button)
|
||||||
|
*/
|
||||||
|
public static interface SwitchControl extends ButtonControl {
|
||||||
|
|
||||||
|
boolean isToggled();
|
||||||
|
|
||||||
|
void setToggled(boolean toggled);
|
||||||
|
|
||||||
|
boolean isDrawBorder();
|
||||||
|
|
||||||
|
void setDrawBorder(boolean draw);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Window closing button
|
||||||
|
*/
|
||||||
|
public static interface WindowCloseButtonControl extends ButtonControl {
|
||||||
|
|
||||||
|
boolean isThickBorder();
|
||||||
|
|
||||||
|
void setThickBorder(boolean thickBorder);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Text field
|
||||||
|
*/
|
||||||
|
public static interface TextfieldControl extends BaseControl {
|
||||||
|
|
||||||
|
RgbaColor getBackgroundColor();
|
||||||
|
|
||||||
|
void setBackgroundColor(RgbaColor color);
|
||||||
|
|
||||||
|
boolean isMultiLine();
|
||||||
|
|
||||||
|
void setMultiLine(boolean multiLine);
|
||||||
|
|
||||||
|
boolean isReadonly();
|
||||||
|
|
||||||
|
void setReadonly(boolean readonly);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Slider
|
||||||
|
*/
|
||||||
|
public static interface SliderControl extends BaseControl {
|
||||||
|
|
||||||
|
RgbaColor getColor();
|
||||||
|
|
||||||
|
void setColor(RgbaColor color);
|
||||||
|
|
||||||
|
double getValue();
|
||||||
|
|
||||||
|
void setValue(double value);
|
||||||
|
|
||||||
|
double getValue2();
|
||||||
|
|
||||||
|
void setValue2(double value2);
|
||||||
|
|
||||||
|
boolean isHorizontal();
|
||||||
|
|
||||||
|
void setHorizontal(boolean horizontal);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Table control
|
||||||
|
*/
|
||||||
|
public static interface TableControl extends BaseControl {
|
||||||
|
|
||||||
|
List<Object> getData();
|
||||||
|
|
||||||
|
void setData(List<Object> data);
|
||||||
|
|
||||||
|
int getRows();
|
||||||
|
|
||||||
|
void setRows(int rows);
|
||||||
|
|
||||||
|
long getVersion();
|
||||||
|
|
||||||
|
void setVersion(long version);
|
||||||
|
|
||||||
|
void setFirstColDoubleSize(boolean bool);
|
||||||
|
|
||||||
|
boolean isFirstColDoubleSize();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets focus on a certain control
|
||||||
|
*/
|
||||||
|
public static interface SetFocusControl extends BaseControl {
|
||||||
|
|
||||||
|
String getFocusId();
|
||||||
|
|
||||||
|
void setFocusId(String id);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows a tooltip
|
||||||
|
*/
|
||||||
|
public static interface TooltipControl extends BaseControl {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||||
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||||
|
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package de.extio.game_engine.renderer.model.bo;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import de.extio.game_engine.renderer.model.DrawEffectRenderingBoEffects;
|
||||||
|
import de.extio.game_engine.renderer.model.RenderingBo;
|
||||||
|
import de.extio.game_engine.spatial2.model.CoordI2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This bo renders on screen effects. Required properties are dependent on the effect (must be set with #setEffect). Best is to search for examples in shipped groovy code.
|
||||||
|
*/
|
||||||
|
public interface DrawEffectRenderingBo extends RenderingBo {
|
||||||
|
|
||||||
|
DrawEffectRenderingBo setEffect(DrawEffectRenderingBoEffects effect);
|
||||||
|
|
||||||
|
public DrawEffectRenderingBo setCustomInt0(int customInt0);
|
||||||
|
|
||||||
|
public DrawEffectRenderingBo setCustomDouble0(double customDouble0);
|
||||||
|
|
||||||
|
public DrawEffectRenderingBo setCustomString0(String customString0);
|
||||||
|
|
||||||
|
List<CoordI2> getRelativeCoordinates();
|
||||||
|
|
||||||
|
DrawEffectRenderingBo setRelativeCoordinates(List<CoordI2> coordinates);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||||
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||||
|
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package de.extio.game_engine.renderer.model.bo;
|
||||||
|
|
||||||
|
import de.extio.game_engine.renderer.model.DrawFontRenderingBoTextAlignment;
|
||||||
|
import de.extio.game_engine.renderer.model.RenderingBoHasDimension;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws text
|
||||||
|
*/
|
||||||
|
public interface DrawFontRenderingBo extends RenderingBoHasDimension {
|
||||||
|
|
||||||
|
DrawFontRenderingBo setText(final String text);
|
||||||
|
|
||||||
|
DrawFontRenderingBo setSize(final int size);
|
||||||
|
|
||||||
|
DrawFontRenderingBo setAlignment(DrawFontRenderingBoTextAlignment alignment);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||||
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||||
|
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package de.extio.game_engine.renderer.model.bo;
|
||||||
|
|
||||||
|
import de.extio.game_engine.renderer.model.RenderingBoHasDimension;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws an image. You can either set the resource name (and optional mod name) or the raw image data as byte[] in png format.
|
||||||
|
*/
|
||||||
|
public interface DrawImageRenderingBo extends RenderingBoHasDimension {
|
||||||
|
|
||||||
|
DrawImageRenderingBo setResourceName(String resourceName);
|
||||||
|
|
||||||
|
DrawImageRenderingBo setImageData(byte[] data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param transparency Values between 0.0 and 1.0
|
||||||
|
*/
|
||||||
|
DrawImageRenderingBo setTransparency(float transparency);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||||
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||||
|
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package de.extio.game_engine.renderer.model.bo;
|
||||||
|
|
||||||
|
import de.extio.game_engine.renderer.model.RenderingBo;
|
||||||
|
|
||||||
|
public interface DrawTestRenderingBo extends RenderingBo {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
|
||||||
|
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||||
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||||
|
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package de.extio.game_engine.renderer.model.bo;
|
||||||
|
|
||||||
|
import de.extio.game_engine.renderer.model.RenderingBoHasDimension;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders a window
|
||||||
|
*/
|
||||||
|
public interface DrawWindowRenderingBo extends RenderingBoHasDimension {
|
||||||
|
|
||||||
|
DrawWindowRenderingBo setThickBorder(boolean thickBorder);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||||
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||||
|
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package de.extio.game_engine.renderer.model.event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message sent by game client engine on any key stroke. You may want to use the respective callback provided by ClientModule instead.
|
||||||
|
*/
|
||||||
|
public final class KeyStrokeEvent {
|
||||||
|
|
||||||
|
private boolean released;
|
||||||
|
|
||||||
|
private int code;
|
||||||
|
|
||||||
|
private String key;
|
||||||
|
|
||||||
|
private int modifiers;
|
||||||
|
|
||||||
|
public KeyStrokeEvent(final boolean released, final int code, final String key, final int modifiers) {
|
||||||
|
this.released = released;
|
||||||
|
this.code = code;
|
||||||
|
this.key = key;
|
||||||
|
this.modifiers = modifiers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isReleased() {
|
||||||
|
return this.released;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setReleased(final boolean released) {
|
||||||
|
this.released = released;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getKey() {
|
||||||
|
return this.key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setKey(final String key) {
|
||||||
|
this.key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getModifiers() {
|
||||||
|
return this.modifiers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setModifiers(final int modifiers) {
|
||||||
|
this.modifiers = modifiers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCode() {
|
||||||
|
return this.code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCode(final int code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||||
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||||
|
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package de.extio.game_engine.renderer.model.event;
|
||||||
|
|
||||||
|
import de.extio.game_engine.spatial2.model.CoordI2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message sent by game client engine on any mouse click
|
||||||
|
*/
|
||||||
|
public final class MouseClickEvent extends MouseEvent {
|
||||||
|
|
||||||
|
private boolean pressed;
|
||||||
|
|
||||||
|
private int button;
|
||||||
|
|
||||||
|
public MouseClickEvent(final boolean pressed, final int modifiers, final int button, final CoordI2 coord) {
|
||||||
|
super(coord, modifiers);
|
||||||
|
this.pressed = pressed;
|
||||||
|
this.button = button;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPressed() {
|
||||||
|
return this.pressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPressed(final boolean pressed) {
|
||||||
|
this.pressed = pressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getButton() {
|
||||||
|
return this.button;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setButton(final int button) {
|
||||||
|
this.button = button;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||||
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||||
|
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package de.extio.game_engine.renderer.model.event;
|
||||||
|
|
||||||
|
import de.extio.game_engine.spatial2.model.CoordI2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message sent by game client engine when mouse enters viewport
|
||||||
|
*/
|
||||||
|
public final class MouseEnterEvent extends MouseEvent {
|
||||||
|
|
||||||
|
public MouseEnterEvent(final int modifiers, final CoordI2 coord) {
|
||||||
|
super(coord, modifiers);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||||
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||||
|
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package de.extio.game_engine.renderer.model.event;
|
||||||
|
|
||||||
|
import de.extio.game_engine.spatial2.model.CoordI2;
|
||||||
|
|
||||||
|
public abstract class MouseEvent {
|
||||||
|
|
||||||
|
private CoordI2 coord;
|
||||||
|
|
||||||
|
private int modifiers;
|
||||||
|
|
||||||
|
public MouseEvent(final CoordI2 coord, final int modifiers) {
|
||||||
|
this.coord = coord;
|
||||||
|
this.modifiers = modifiers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CoordI2 getCoord() {
|
||||||
|
return this.coord;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCoord(final CoordI2 coord) {
|
||||||
|
this.coord = coord;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getModifiers() {
|
||||||
|
return this.modifiers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setModifiers(final int modifiers) {
|
||||||
|
this.modifiers = modifiers;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||||
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||||
|
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package de.extio.game_engine.renderer.model.event;
|
||||||
|
|
||||||
|
import de.extio.game_engine.spatial2.model.CoordI2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Message sent by game client engine when mouse has been moved
|
||||||
|
*/
|
||||||
|
public final class MouseMoveEvent extends MouseEvent {
|
||||||
|
|
||||||
|
private boolean drag;
|
||||||
|
|
||||||
|
private int button;
|
||||||
|
|
||||||
|
public MouseMoveEvent(final boolean drag, final int modifiers, final CoordI2 coord, final int button) {
|
||||||
|
super(coord, modifiers);
|
||||||
|
this.drag = drag;
|
||||||
|
this.button = button;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDrag() {
|
||||||
|
return this.drag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDrag(final boolean drag) {
|
||||||
|
this.drag = drag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getButton() {
|
||||||
|
return this.button;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setButton(final int button) {
|
||||||
|
this.button = button;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||||
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||||
|
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package de.extio.game_engine.renderer.model.event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This event contains a screenshot created by the renderer. Screenshot creation can be triggered via RendererControl
|
||||||
|
*/
|
||||||
|
public final class TakeScreenshotEvent {
|
||||||
|
|
||||||
|
private final String id;
|
||||||
|
|
||||||
|
private final byte[] payload;
|
||||||
|
|
||||||
|
public TakeScreenshotEvent(final String id, final byte[] payload) {
|
||||||
|
this.id = id;
|
||||||
|
this.payload = payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return this.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getPayload() {
|
||||||
|
return this.payload;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||||
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||||
|
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package de.extio.game_engine.renderer.model.event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This event is fired when a user interacted with a control in the UI, for example clicked on a button, entered text, ...
|
||||||
|
*/
|
||||||
|
public final class UiControlEvent {
|
||||||
|
|
||||||
|
private final String id;
|
||||||
|
|
||||||
|
private final Object payload;
|
||||||
|
|
||||||
|
public UiControlEvent(final String id, final Object payload) {
|
||||||
|
this.id = id;
|
||||||
|
this.payload = payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the control ID
|
||||||
|
*/
|
||||||
|
public String getId() {
|
||||||
|
return this.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Payload depends on the type of action and the control
|
||||||
|
*/
|
||||||
|
public Object getPayload() {
|
||||||
|
return this.payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
832
src/main/java/de/extio/game_engine/spatial2/WorldUtils2.java
Normal file
832
src/main/java/de/extio/game_engine/spatial2/WorldUtils2.java
Normal file
@@ -0,0 +1,832 @@
|
|||||||
|
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||||
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||||
|
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package de.extio.game_engine.spatial2;
|
||||||
|
|
||||||
|
import java.awt.Rectangle;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import de.extio.game_engine.spatial2.model.Area2;
|
||||||
|
import de.extio.game_engine.spatial2.model.CoordD2;
|
||||||
|
import de.extio.game_engine.spatial2.model.CoordI2;
|
||||||
|
import de.extio.game_engine.spatial2.model.Edge2;
|
||||||
|
import de.extio.game_engine.spatial2.model.HasPosition2;
|
||||||
|
import de.extio.game_engine.spatial2.model.HasPositionAndDimension2;
|
||||||
|
import de.extio.game_engine.spatial2.model.ImmutableCoordD2;
|
||||||
|
import de.extio.game_engine.spatial2.model.ImmutableCoordI2;
|
||||||
|
import de.extio.game_engine.spatial2.model.MutableCoordD2;
|
||||||
|
import de.extio.game_engine.spatial2.model.MutableCoordI2;
|
||||||
|
import de.extio.game_engine.spatial2.model.RectI2;
|
||||||
|
import de.extio.game_engine.util.rng.ThreadLocalXorShift128Random;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Library for geometric problems
|
||||||
|
*/
|
||||||
|
public final class WorldUtils2 {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Pythagorean theorem
|
||||||
|
* @param v Vector
|
||||||
|
* @return Length of vector v
|
||||||
|
*/
|
||||||
|
public static double getDistance(final CoordI2 v) {
|
||||||
|
return Math.sqrt(Math.pow(v.getX(), 2.0) + Math.pow(v.getY(), 2.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Pythagorean theorem
|
||||||
|
* @param v Vector
|
||||||
|
* @return Length of vector v
|
||||||
|
*/
|
||||||
|
public static double getDistance(final CoordD2 v) {
|
||||||
|
return Math.sqrt(Math.pow(v.getX(), 2.0) + Math.pow(v.getY(), 2.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Pythagorean theorem
|
||||||
|
* @param p0 Coordinate 0
|
||||||
|
* @param p1 Coordinate 1
|
||||||
|
* @return Distance
|
||||||
|
*/
|
||||||
|
public static double getDistance(final HasPosition2 p0, final HasPosition2 p1) {
|
||||||
|
return getDistance(p0.getPosition(), p1.getPosition());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Pythagorean theorem
|
||||||
|
* @param c0 Coordinate 0
|
||||||
|
* @param c1 Coordinate 1
|
||||||
|
* @return Distance
|
||||||
|
*/
|
||||||
|
public static double getDistance(final CoordI2 c0, final CoordI2 c1) {
|
||||||
|
return Math.sqrt(Math.pow(c0.getX() - c1.getX(), 2.0) + Math.pow(c0.getY() - c1.getY(), 2.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Pythagorean theorem
|
||||||
|
* @param c0 Coordinate 0
|
||||||
|
* @param c1 Coordinate 1
|
||||||
|
* @return Distance
|
||||||
|
*/
|
||||||
|
public static double getDistance(final CoordD2 c0, final CoordD2 c1) {
|
||||||
|
return Math.sqrt(Math.pow(c0.getX() - c1.getX(), 2.0) + Math.pow(c0.getY() - c1.getY(), 2.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the angle between 2 vectors
|
||||||
|
* @param v0 Vector 0
|
||||||
|
* @param v1 Vector 1
|
||||||
|
* @return Angle (radians)
|
||||||
|
*/
|
||||||
|
public static double getVectorAngle(final CoordI2 v0, final CoordI2 v1) {
|
||||||
|
return Math.atan2(v0.getX() * v1.getY() - v0.getY() * v1.getX(), v0.getX() * v1.getX() + v0.getY() * v1.getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the angle between 2 vectors
|
||||||
|
* @param v0 Vector 0
|
||||||
|
* @param v1 Vector 1
|
||||||
|
* @return Angle (radians)
|
||||||
|
*/
|
||||||
|
public static double getVectorAngle(final CoordD2 v0, final CoordD2 v1) {
|
||||||
|
// final double dotV = v0.getX() * v1.getX() + v0.getY() * v1.getY();
|
||||||
|
// final double distV = getDistance(v0) * getDistance(v1);
|
||||||
|
// if (distV == 0.0) {
|
||||||
|
// return 0.0;
|
||||||
|
// }
|
||||||
|
// return Math.acos(Math.min(dotV / distV, 1.0));
|
||||||
|
return Math.atan2(v0.getX() * v1.getY() - v0.getY() * v1.getX(), v0.getX() * v1.getX() + v0.getY() * v1.getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the direction of a vector
|
||||||
|
* @param v0 Vector 0
|
||||||
|
* @param v1 Vector 1
|
||||||
|
* @return Angle (radians)
|
||||||
|
*/
|
||||||
|
public static double getVectorAngle(final CoordI2 v) {
|
||||||
|
return Math.atan2(v.getX(), v.getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the direction of a vector
|
||||||
|
* @param v0 Vector 0
|
||||||
|
* @param v1 Vector 1
|
||||||
|
* @return Angle (radians)
|
||||||
|
*/
|
||||||
|
public static double getVectorAngle(final CoordD2 v) {
|
||||||
|
return Math.atan2(v.getX(), v.getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the dot product
|
||||||
|
* @param v0 Vector 0
|
||||||
|
* @param v1 Vector 1
|
||||||
|
* @return Dot product
|
||||||
|
*/
|
||||||
|
public static double getVectorDotProduct(final CoordD2 v0, final CoordD2 v1) {
|
||||||
|
return v0.getX() * v1.getX() + v0.getY() * v1.getY();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rotates a vector
|
||||||
|
* @return Rotated vector
|
||||||
|
*/
|
||||||
|
public static CoordD2 rotateVector(final CoordD2 v, final double θ) {
|
||||||
|
return ImmutableCoordD2.create(v.getX() * Math.cos(θ) - v.getY() * Math.sin(θ), v.getX() * Math.sin(θ) + v.getY() * Math.cos(θ));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether coord is within bounds (e.g. composite entity)
|
||||||
|
* @param hasPosition
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static boolean isInBounds(final HasPosition2 hasPosition, final CoordI2 dimension) {
|
||||||
|
return isInBounds(hasPosition.getPosition(), dimension);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether coord is within bounds (e.g. composite entity)
|
||||||
|
* @param coord
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static boolean isInBounds(final CoordI2 coord, final CoordI2 dimension) {
|
||||||
|
if (coord == null ||
|
||||||
|
coord.getX() < 0 || coord.getX() >= dimension.getX() ||
|
||||||
|
coord.getY() < 0 || coord.getY() >= dimension.getY()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signum function that returns -1 or 1.
|
||||||
|
* @param n
|
||||||
|
* @return -1 if n is negative and 1 if n is positive (including 0)
|
||||||
|
*/
|
||||||
|
public static int copySign(final int n) {
|
||||||
|
return (Integer.signum(n) >> 31) | 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signum function that returns -1 or 1.
|
||||||
|
* @param n
|
||||||
|
* @return -1 if n is negative and 1 if n is positive (including 0)
|
||||||
|
*/
|
||||||
|
public static int copySign(final double n) {
|
||||||
|
return ((int) Math.signum(n) >> 31) | 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Intersects 2 recangles
|
||||||
|
* <!-- @deprecated Use Area2 based function intersectAreas() instead -->
|
||||||
|
* @param r0 Rectangle 0
|
||||||
|
* @param r1 Rectangle 1
|
||||||
|
* @return The intersected area if r0 and r1 overlap, else null
|
||||||
|
*/
|
||||||
|
// @Deprecated
|
||||||
|
public static RectI2 intersectRectangles(final RectI2 r0, final RectI2 r1) {
|
||||||
|
final CoordI2 size0 = ImmutableCoordI2.create(r0.getC1().getX() - r0.getC0().getX(), r0.getC1().getY() - r0.getC0().getY());
|
||||||
|
final CoordI2 size1 = ImmutableCoordI2.create(r1.getC1().getX() - r1.getC0().getX(), r1.getC1().getY() - r1.getC0().getY());
|
||||||
|
final Rectangle rect0 = new Rectangle(r0.getC0().getX(), r0.getC0().getY(), size0.getX(), size0.getY());
|
||||||
|
final Rectangle rect1 = new Rectangle(r1.getC0().getX(), r1.getC0().getY(), size1.getX(), size1.getY());
|
||||||
|
|
||||||
|
final Rectangle intersection = rect0.intersection(rect1);
|
||||||
|
if (intersection.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new RectI2(
|
||||||
|
ImmutableCoordI2.create((int) intersection.getX(), (int) intersection.getY()),
|
||||||
|
ImmutableCoordI2.create((int) (intersection.getX() + intersection.getWidth()), (int) (intersection.getY() + intersection.getHeight())));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Intersects 2 areas
|
||||||
|
* @param r0 Area 0
|
||||||
|
* @param r1 Area 1
|
||||||
|
* @return The intersected area if r0 and r1 overlap, else null
|
||||||
|
*/
|
||||||
|
public static Area2 intersectAreas(final HasPositionAndDimension2 a0, final HasPositionAndDimension2 a1) {
|
||||||
|
final Rectangle rect0 = new Rectangle(a0.getPosition().getX(), a0.getPosition().getY(), a0.getDimension().getX(), a0.getDimension().getY());
|
||||||
|
final Rectangle rect1 = new Rectangle(a1.getPosition().getX(), a1.getPosition().getY(), a1.getDimension().getX(), a1.getDimension().getY());
|
||||||
|
|
||||||
|
final Rectangle intersection = rect0.intersection(rect1);
|
||||||
|
if (intersection.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Area2(
|
||||||
|
ImmutableCoordI2.create((int) intersection.getX(), (int) intersection.getY()),
|
||||||
|
ImmutableCoordI2.create((int) intersection.getWidth(), (int) intersection.getHeight()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether 2 objects intersect
|
||||||
|
* @param obj0 HasPositionAndDimension2 object 0
|
||||||
|
* @param obj1 HasPositionAndDimension2 object 1
|
||||||
|
* @return true / false whether obj0 intersects with obj1
|
||||||
|
*/
|
||||||
|
public static boolean intersects(final HasPositionAndDimension2 obj0, final HasPositionAndDimension2 obj1) {
|
||||||
|
return intersects(obj0.getPosition(), obj0.getDimension(), obj1.getPosition(), obj1.getDimension());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether 2 objects intersect
|
||||||
|
* @param position0 position of object 0
|
||||||
|
* @param dimension0 dimension of object 0
|
||||||
|
* @param position1 position of object 1
|
||||||
|
* @param dimension1 dimension of object 1
|
||||||
|
* @return true / false whether obj0 intersects with obj1
|
||||||
|
*/
|
||||||
|
public static boolean intersects(final CoordI2 position0, final CoordI2 dimension0, final CoordI2 position1, final CoordI2 dimension1) {
|
||||||
|
int d0x = dimension0.getX();
|
||||||
|
int d0y = dimension0.getY();
|
||||||
|
int d1x = dimension1.getX();
|
||||||
|
int d1y = dimension1.getY();
|
||||||
|
if (d0x <= 0 || d0y <= 0 || d1x <= 0 || d1y <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
d0x += position0.getX();
|
||||||
|
d0y += position0.getY();
|
||||||
|
d1x += position1.getX();
|
||||||
|
d1y += position1.getY();
|
||||||
|
|
||||||
|
return ((d0x < position0.getX() || d0x > position1.getX()) &&
|
||||||
|
(d0y < position0.getY() || d0y > position1.getY()) &&
|
||||||
|
(d1x < position1.getX() || d1x > position0.getX()) &&
|
||||||
|
(d1y < position1.getY() || d1y > position0.getY()));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds an aligned Area2 from arbitrary points
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static Area2 pointsToArea(final CoordI2... points) {
|
||||||
|
if (points == null || points.length == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final CoordI2 corner0 = MutableCoordI2.create(points[0]);
|
||||||
|
final CoordI2 corner1 = MutableCoordI2.create(points[0]).add(1, 1);
|
||||||
|
for (int i = 1; i < points.length; i++) {
|
||||||
|
corner0.setXY(Math.min(corner0.getX(), points[i].getX()), Math.min(corner0.getY(), points[i].getY()));
|
||||||
|
corner1.setXY(Math.max(corner1.getX(), points[i].getX() + 1), Math.max(corner1.getY(), points[i].getY() + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
final CoordI2 dimension = ImmutableCoordI2.create(Math.max(1, corner1.getX() - corner0.getX()), Math.max(1, corner1.getY() - corner0.getY()));
|
||||||
|
return new Area2(ImmutableCoordI2.create(corner0), dimension);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the point of intersection to intercept target from origin with velocity velOrigin
|
||||||
|
* @param pOrigin_ Position of origin (e.g. ship that intercepts target)
|
||||||
|
* @param velOrigin Velocity of origin
|
||||||
|
* @param pTarget_ Position of target
|
||||||
|
* @param vTarget_ Velocity vector of target
|
||||||
|
* @return Point of interception or null if interception is not possible
|
||||||
|
*/
|
||||||
|
public static CoordI2 intercept(final CoordI2 pOrigin_, final double velOrigin, final CoordI2 pTarget_, final CoordD2 vTarget_) {
|
||||||
|
final CoordD2 pOrigin = ImmutableCoordD2.create(pOrigin_);
|
||||||
|
final CoordD2 pTarget = ImmutableCoordD2.create(pTarget_);
|
||||||
|
final CoordD2 vTarget = ImmutableCoordD2.create(vTarget_);
|
||||||
|
|
||||||
|
final double a = (vTarget.getX() * vTarget.getX()) + (vTarget.getY() * vTarget.getY()) - (velOrigin * velOrigin);
|
||||||
|
final double b = 2 * ((pTarget.getX() * vTarget.getX()) + (pTarget.getY() * vTarget.getY()) - (pOrigin.getX() * vTarget.getX()) - (pOrigin.getY() * vTarget.getY()));
|
||||||
|
final double c = (pTarget.getX() * pTarget.getX()) + (pTarget.getY() * pTarget.getY()) + (pOrigin.getX() * pOrigin.getX()) + (pOrigin.getY() * pOrigin.getY()) - (2 * pOrigin.getX() * pTarget.getX()) - (2 * pOrigin.getY() * pTarget.getY());
|
||||||
|
final double t0 = (-b + Math.sqrt((b * b) - (4.0 * a * c))) / (2.0 * a);
|
||||||
|
final double t1 = (-b - Math.sqrt((b * b) - (4.0 * a * c))) / (2.0 * a);
|
||||||
|
|
||||||
|
final boolean t0Pass = !Double.isNaN(t0) && t0 > 0.0;
|
||||||
|
final boolean t1Pass = !Double.isNaN(t1) && t1 > 0.0;
|
||||||
|
double t;
|
||||||
|
if (t0Pass && t1Pass) {
|
||||||
|
t = Math.min(t0, t1);
|
||||||
|
}
|
||||||
|
else if (t0Pass) {
|
||||||
|
t = t0;
|
||||||
|
}
|
||||||
|
else if (t1Pass) {
|
||||||
|
t = t1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ImmutableCoordI2.create(MutableCoordD2.create(vTarget).multiply(t).add(pTarget));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the sector the point is located from source's view
|
||||||
|
* @param point
|
||||||
|
* @param source
|
||||||
|
* @return Sector (Edge2)
|
||||||
|
*/
|
||||||
|
public static Edge2 getSector(final CoordI2 point, final HasPositionAndDimension2 source) {
|
||||||
|
if (ImmutableCoordI2.zero().equals(source.getDimension())) {
|
||||||
|
throw new IllegalArgumentException("Source's dimension is zero");
|
||||||
|
}
|
||||||
|
|
||||||
|
final DistanceAndSector dists[] = new DistanceAndSector[4];
|
||||||
|
final CoordI2 targetPos = ImmutableCoordI2.create(source.getPosition());
|
||||||
|
|
||||||
|
dists[0] = new DistanceAndSector(getDistance(point, targetPos), 0b1);
|
||||||
|
dists[1] = new DistanceAndSector(getDistance(point, targetPos.add(source.getDimension().getX(), 0)), 0b10);
|
||||||
|
dists[2] = new DistanceAndSector(getDistance(point, targetPos.add(0, source.getDimension().getY())), 0b100);
|
||||||
|
dists[3] = new DistanceAndSector(getDistance(point, targetPos.add(source.getDimension().getX(), source.getDimension().getY())), 0b1000);
|
||||||
|
|
||||||
|
Arrays.sort(dists, (e0, e1) -> Double.compare(e0.dist, e1.dist));
|
||||||
|
|
||||||
|
final int sector = dists[0].sector | dists[1].sector;
|
||||||
|
|
||||||
|
if (sector == 0b11) {
|
||||||
|
return Edge2.TOP;
|
||||||
|
}
|
||||||
|
else if (sector == 0b101) {
|
||||||
|
return Edge2.LEFT;
|
||||||
|
}
|
||||||
|
else if (sector == 0b1010) {
|
||||||
|
return Edge2.RIGHT;
|
||||||
|
}
|
||||||
|
else if (sector == 0b1100) {
|
||||||
|
return Edge2.BOTTOM;
|
||||||
|
}
|
||||||
|
throw new RuntimeException("Unknown sector 0b" + Integer.toString(sector, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the pedal of altitude c of triangle A.B,C
|
||||||
|
* @param a Point A
|
||||||
|
* @param b Point B
|
||||||
|
* @param c Point C
|
||||||
|
* @return Pedal of altitude c
|
||||||
|
*/
|
||||||
|
public static CoordD2 getPedal(final CoordI2 a, final CoordI2 b, final CoordI2 c) {
|
||||||
|
final double distA = getDistance(a, b);
|
||||||
|
if (distA == 0.0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final double distASquare = Math.pow(distA, 2.0);
|
||||||
|
final double distB = getDistance(b, c);
|
||||||
|
final double distBSquare = Math.pow(distB, 2.0);
|
||||||
|
final double distC = getDistance(c, a);
|
||||||
|
final double distCSquare = Math.pow(distC, 2.0);
|
||||||
|
|
||||||
|
final double altA = Math.sqrt((2.0 * ((distASquare * distBSquare) + (distBSquare * distCSquare) + (distCSquare * distASquare))) - (Math.pow(distA, 4.0) + Math.pow(distB, 4.0) + Math.pow(distC, 4.0))) / (2.0 * distA);
|
||||||
|
if (Double.isNaN(altA)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final double alpha = getVectorAngle(b.substract(a), c.substract(a));
|
||||||
|
final double scaleA = (Math.sqrt(distCSquare - Math.pow(altA, 2.0)) / distA) * copySign(Math.PI / 2.0 - Math.abs(alpha));
|
||||||
|
if (Double.isNaN(scaleA)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ImmutableCoordD2.create(
|
||||||
|
(((double) b.getX() - (double) a.getX()) * scaleA) + a.getX(),
|
||||||
|
(((double) b.getY() - (double) a.getY()) * scaleA) + a.getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get's the pedal of altitude c of triangle A.B,C or the closest point on line segment a,b
|
||||||
|
* <pre>
|
||||||
|
* a-----p2-------b p1
|
||||||
|
* | \ |
|
||||||
|
* | \ |
|
||||||
|
* | \|
|
||||||
|
* c2 c1
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* Returns null if there is no pedal.
|
||||||
|
*/
|
||||||
|
public static CoordD2 getPedalOnLineSegmentAB(final CoordI2 a, final CoordI2 b, final CoordI2 c) {
|
||||||
|
final CoordD2 pedal = getPedal(a, b, c);
|
||||||
|
if (pedal == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
double x = pedal.getX();
|
||||||
|
final double minX = Math.min(a.getX(), b.getX());
|
||||||
|
if (x < minX) {
|
||||||
|
x = minX;
|
||||||
|
}
|
||||||
|
final double maxX = Math.max(a.getX(), b.getX());
|
||||||
|
if (x > maxX) {
|
||||||
|
x = maxX;
|
||||||
|
}
|
||||||
|
|
||||||
|
double y = pedal.getY();
|
||||||
|
final double minY = Math.min(a.getY(), b.getY());
|
||||||
|
if (y < minY) {
|
||||||
|
y = minY;
|
||||||
|
}
|
||||||
|
final double maxY = Math.max(a.getY(), b.getY());
|
||||||
|
if (y > maxY) {
|
||||||
|
y = maxY;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ImmutableCoordD2.create(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get's the closest point at area target from origin
|
||||||
|
* <pre>
|
||||||
|
* *-----*
|
||||||
|
* | |
|
||||||
|
* | x _______ origin
|
||||||
|
* | |
|
||||||
|
* *-----*
|
||||||
|
* </pre>
|
||||||
|
* @param origin
|
||||||
|
* @param target
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static CoordI2 getClosestPoint(final CoordI2 origin, final HasPositionAndDimension2 target) {
|
||||||
|
double minDistance = Double.MAX_VALUE;
|
||||||
|
CoordI2 result = null;
|
||||||
|
|
||||||
|
final List<CoordI2> vertices = getVertices(target);
|
||||||
|
for (int i = 1; i < 4; i++) {
|
||||||
|
final CoordD2 pedal = getPedalOnLineSegmentAB(vertices.get(i - 1), vertices.get(i), origin);
|
||||||
|
if (pedal != null) {
|
||||||
|
final CoordI2 pedalI = pedal.toImmutableCoordI2();
|
||||||
|
final double distance = getDistance(origin, pedalI);
|
||||||
|
if (distance < minDistance) {
|
||||||
|
minDistance = distance;
|
||||||
|
result = pedalI;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates waypoints to evade a collision between origin and target.
|
||||||
|
* @param pOrigin Origin
|
||||||
|
* @param velOrigin Origin velocity
|
||||||
|
* @param vOrigin Origin velocity vector, origin course not necessarily the current velocity vector
|
||||||
|
* @param pTarget Target
|
||||||
|
* @param vTarget Target velocity vector
|
||||||
|
* @param oTarget Collision offset on target (entityPos)
|
||||||
|
* @param pInterception (Optional) Point of interception. Can be null, in this case it is calculated based on the velocity of source and the velocity vector of target.
|
||||||
|
* @return Waypoints for evasion or null if there is no collision
|
||||||
|
*/
|
||||||
|
public static List<CoordI2> evade(final HasPositionAndDimension2 origin, final double velOrigin, final CoordD2 vOrigin, final HasPositionAndDimension2 target, final CoordD2 vTarget, final CoordI2 oTarget, final CoordI2 pInterception_) {
|
||||||
|
final double THRESHOLD_NOT_MOVING = 0.2;
|
||||||
|
|
||||||
|
final CoordI2 pOrigin = ImmutableCoordI2.create(origin.getPosition()).add(ImmutableCoordI2.create(origin.getDimension()).divide(2));
|
||||||
|
final CoordI2 pTarget = ImmutableCoordI2.create(target.getPosition()).add(ImmutableCoordI2.create(target.getDimension()).divide(2));
|
||||||
|
final double velTarget = getDistance(vTarget);
|
||||||
|
|
||||||
|
if (velOrigin < THRESHOLD_NOT_MOVING) {
|
||||||
|
// Corner case: Origin is not moving
|
||||||
|
|
||||||
|
final CoordD2 vOriginToTarget = ImmutableCoordD2.create(pTarget).substract(ImmutableCoordD2.create(pOrigin));
|
||||||
|
CoordD2 vEvasion = MutableCoordD2
|
||||||
|
.create(-vOriginToTarget.getY(), vOriginToTarget.getX()) // Rotate by PI/2
|
||||||
|
.toVNorm()
|
||||||
|
.multiply(getDistance(origin.getDimension()) * 2.0);
|
||||||
|
vEvasion = vEvasion.setXY(Math.ceil(vEvasion.getX()), Math.ceil(vEvasion.getY()));
|
||||||
|
return List.of(pOrigin.add(ImmutableCoordI2.create(vEvasion)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Point of interception
|
||||||
|
|
||||||
|
CoordI2 pInterception;
|
||||||
|
if (pInterception_ == null) {
|
||||||
|
pInterception = intercept(pOrigin, velOrigin, pTarget, vTarget);
|
||||||
|
if (pInterception == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
pInterception = pInterception_;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create vertices
|
||||||
|
|
||||||
|
final CoordI2 boxDim = ImmutableCoordI2.create(
|
||||||
|
Math.max(origin.getDimension().getX(), origin.getDimension().getY()) + 4,
|
||||||
|
Math.max(origin.getDimension().getX(), origin.getDimension().getY()) + 4);
|
||||||
|
final CoordI2 targetDimHalf = target.getDimension().divide(2);
|
||||||
|
final Area2 box = new Area2(pInterception
|
||||||
|
.add(targetDimHalf.substract(oTarget))
|
||||||
|
.substract(targetDimHalf)
|
||||||
|
.substract(boxDim),
|
||||||
|
targetDimHalf
|
||||||
|
.add(boxDim)
|
||||||
|
.multiply(2));
|
||||||
|
final List<CoordI2> vertices = getVertices(box);
|
||||||
|
|
||||||
|
// Rule out one or more vertices, because ...
|
||||||
|
|
||||||
|
if (velTarget >= THRESHOLD_NOT_MOVING && Math.abs(getVectorAngle(vOrigin, vTarget)) < Math.PI / 4.0) { // Velocity vectors are similar -> collision from behind
|
||||||
|
// Remove both vertices in origin move direction
|
||||||
|
|
||||||
|
int removeVertexIdxLow = -1;
|
||||||
|
int removeVertexIdxHigh = -1;
|
||||||
|
|
||||||
|
double aLow = Math.PI;
|
||||||
|
double aHigh = 0.0;
|
||||||
|
for (int i = 0; i < vertices.size(); i++) {
|
||||||
|
final CoordI2 curVertex = vertices.get(i);
|
||||||
|
|
||||||
|
final CoordD2 direction = pInterception.substract(curVertex).toImmutableCoordD2();
|
||||||
|
final double a = Math.abs(getVectorAngle(vOrigin, direction));
|
||||||
|
if (removeVertexIdxLow == -1 || a < aLow) {
|
||||||
|
aLow = a;
|
||||||
|
removeVertexIdxLow = i;
|
||||||
|
}
|
||||||
|
if (removeVertexIdxHigh == -1 || a > aHigh) {
|
||||||
|
aHigh = a;
|
||||||
|
removeVertexIdxHigh = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vertices.set(removeVertexIdxLow, null);
|
||||||
|
vertices.set(removeVertexIdxHigh, null);
|
||||||
|
}
|
||||||
|
else if (velTarget < velOrigin - 0.75) { // Origin speed is higher
|
||||||
|
// Remove vertex farthest from origin move direction, but closest vertex will never be ruled out (-> primary)
|
||||||
|
|
||||||
|
int safeVertexIdx = 0;
|
||||||
|
double vLen = -1.0;
|
||||||
|
for (int i = 0; i < vertices.size(); i++) {
|
||||||
|
final CoordI2 curVertex = vertices.get(i);
|
||||||
|
final double curVLen = getDistance(pOrigin, curVertex);
|
||||||
|
if (vLen < 0.0 || curVLen < vLen) {
|
||||||
|
vLen = curVLen;
|
||||||
|
safeVertexIdx = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int removeVertexIdx = 0;
|
||||||
|
double aHigh = 0.0;
|
||||||
|
for (int i = 0; i < vertices.size(); i++) {
|
||||||
|
if (i == safeVertexIdx) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final CoordI2 curVertex = vertices.get(i);
|
||||||
|
final double a = Math.abs(getVectorAngle(curVertex.substract(pOrigin).toImmutableCoordD2(), vOrigin));
|
||||||
|
if (a > aHigh) {
|
||||||
|
aHigh = a;
|
||||||
|
removeVertexIdx = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vertices.set(removeVertexIdx, null);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Remove vertex closest to target move direction
|
||||||
|
|
||||||
|
int removeVertexIdx = -1;
|
||||||
|
|
||||||
|
double vLen = -1.0;
|
||||||
|
for (int i = 0; i < vertices.size(); i++) {
|
||||||
|
final CoordI2 curVertex = vertices.get(i);
|
||||||
|
final CoordD2 curV = ImmutableCoordD2.create(pInterception.substract(curVertex)).add(vTarget);
|
||||||
|
final double curVLen = getDistance(curV);
|
||||||
|
if (vLen < 0.0 || curVLen < vLen) {
|
||||||
|
vLen = curVLen;
|
||||||
|
removeVertexIdx = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vertices.set(removeVertexIdx, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get closest vertex -> primary
|
||||||
|
|
||||||
|
int primaryVertexIdx = -1;
|
||||||
|
double closestDist = -1.0;
|
||||||
|
for (int i = 0; i < vertices.size(); i++) {
|
||||||
|
if (vertices.get(i) != null) {
|
||||||
|
final double curDist = getDistance(vertices.get(i), pOrigin);
|
||||||
|
if (closestDist < 0.0 || curDist < closestDist) {
|
||||||
|
closestDist = curDist;
|
||||||
|
primaryVertexIdx = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Choose secondary vertex
|
||||||
|
|
||||||
|
final int candidate0 = (primaryVertexIdx + 1) % vertices.size();
|
||||||
|
final int candidate1 = Math.floorMod(primaryVertexIdx - 1, vertices.size());
|
||||||
|
int secondVertexIdx;
|
||||||
|
if (vertices.get(candidate0) != null && vertices.get(candidate1) != null) {
|
||||||
|
// Center vertex, choose secondary which is closer to origin velocity vector
|
||||||
|
final double a0 = getVectorAngle(pInterception.substract(pOrigin), vertices.get(candidate0).substract(pOrigin));
|
||||||
|
final double a1 = getVectorAngle(pInterception.substract(pOrigin), vertices.get(candidate1).substract(pOrigin));
|
||||||
|
if (Math.abs(a0) < Math.abs(a1)) {
|
||||||
|
secondVertexIdx = candidate0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
secondVertexIdx = candidate1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Outer vertex, there is only one choice for the secondary one
|
||||||
|
else if (vertices.get(candidate0) != null) {
|
||||||
|
secondVertexIdx = candidate0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
secondVertexIdx = candidate1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether path from primary vertex to secondary intersects with origin path -> in this case skip the primary vertex except the closest vertex is in target velocity direction
|
||||||
|
|
||||||
|
// final double a0 = getVectorAngle(pInterception.substract(pOrigin), vertices.get(primaryVertexIdx).substract(pOrigin));
|
||||||
|
// final double a1 = getVectorAngle(pInterception.substract(pOrigin), vertices.get(secondVertexIdx).substract(pOrigin));
|
||||||
|
// final double intersecting = Math.signum(a0 * a1);
|
||||||
|
// if (intersecting < 0.0) {
|
||||||
|
// if (closestDist < getDistance(removedVertex, pOrigin)) {
|
||||||
|
// primaryVertexIdx = secondVertexIdx;
|
||||||
|
// secondVertexIdx = -1;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (secondVertexIdx == -1 || vertices.get(secondVertexIdx) == null) {
|
||||||
|
return List.of(vertices.get(primaryVertexIdx));
|
||||||
|
}
|
||||||
|
return List.of(vertices.get(primaryVertexIdx), vertices.get(secondVertexIdx));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <pre>
|
||||||
|
* x-----x
|
||||||
|
* | |
|
||||||
|
* | |
|
||||||
|
* x-----x
|
||||||
|
* </pre>
|
||||||
|
* Returns the 4 vertices of a rectangle
|
||||||
|
* @param obj Rectangle
|
||||||
|
* @return 4 vertices ordered clock-wise
|
||||||
|
*/
|
||||||
|
public static List<CoordI2> getVertices(final HasPositionAndDimension2 obj) {
|
||||||
|
final List<CoordI2> result = new ArrayList<>(4);
|
||||||
|
final CoordI2 objPos = ImmutableCoordI2.create(obj.getPosition());
|
||||||
|
result.add(objPos);
|
||||||
|
result.add(objPos.add(obj.getDimension().getX(), 0));
|
||||||
|
result.add(objPos.add(obj.getDimension()));
|
||||||
|
result.add(objPos.add(0, obj.getDimension().getY()));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <pre>
|
||||||
|
* x-----x
|
||||||
|
* | |
|
||||||
|
* | |
|
||||||
|
* x-----x
|
||||||
|
* </pre>
|
||||||
|
* Returns the 4 sides of a rectangle
|
||||||
|
* @param obj Rectangle
|
||||||
|
* @return 4 sides ordered clock-wise
|
||||||
|
*/
|
||||||
|
public static List<Area2> getSides(final HasPositionAndDimension2 obj) {
|
||||||
|
final List<Area2> result = new ArrayList<>(4);
|
||||||
|
final CoordI2 objPos = ImmutableCoordI2.create(obj.getPosition());
|
||||||
|
result.add(new Area2(objPos, ImmutableCoordI2.create(obj.getDimension().getX(), 1)));
|
||||||
|
result.add(new Area2(objPos.add(obj.getDimension().getX(), 0), ImmutableCoordI2.create(1, obj.getDimension().getY())));
|
||||||
|
result.add(new Area2(objPos.add(1, obj.getDimension().getY()), ImmutableCoordI2.create(obj.getDimension().getX(), 1)));
|
||||||
|
result.add(new Area2(objPos.add(0, 1), ImmutableCoordI2.create(1, obj.getDimension().getY())));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a random point in a circle
|
||||||
|
* @param center of the circle
|
||||||
|
* @param radius of the circle
|
||||||
|
* @return random point in the circle
|
||||||
|
*/
|
||||||
|
public static CoordI2 getRandomPointInCircle(final CoordI2 center, final int radius) {
|
||||||
|
return getRandomPointInCircle(center, radius, ThreadLocalXorShift128Random.current());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a point in a circle
|
||||||
|
* @param center of the circle
|
||||||
|
* @param radius of the circle
|
||||||
|
* @param random rng
|
||||||
|
* @return random point in the circle
|
||||||
|
*/
|
||||||
|
public static CoordI2 getRandomPointInCircle(final CoordI2 center, final int radius, final Random random) {
|
||||||
|
return MutableCoordD2
|
||||||
|
.create(Math.cos(random.nextDouble() * Math.PI * 2.0 - Math.PI), Math.sin(random.nextDouble() * Math.PI * 2.0 - Math.PI))
|
||||||
|
.multiply(radius)
|
||||||
|
.toImmutableCoordI2()
|
||||||
|
.add(center);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a random point on a circle
|
||||||
|
* @param center of the circle
|
||||||
|
* @param radius of the circle
|
||||||
|
* @return random point in the circle
|
||||||
|
*/
|
||||||
|
public static CoordI2 getRandomPointOnCircle(final CoordI2 center, final int radius) {
|
||||||
|
return getRandomPointOnCircle(center, radius, ThreadLocalXorShift128Random.current());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a random point on a circle
|
||||||
|
* @param center of the circle
|
||||||
|
* @param radius of the circle
|
||||||
|
* @param random rng
|
||||||
|
* @return random point in the circle
|
||||||
|
*/
|
||||||
|
public static CoordI2 getRandomPointOnCircle(final CoordI2 center, final int radius, final Random random) {
|
||||||
|
final double angle = random.nextDouble() * Math.PI * 2.0 - Math.PI;
|
||||||
|
return MutableCoordD2
|
||||||
|
.create(Math.cos(angle), Math.sin(angle))
|
||||||
|
.multiply(radius)
|
||||||
|
.toImmutableCoordI2()
|
||||||
|
.add(center);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a reproducible point on a circle
|
||||||
|
* @param center of the circle
|
||||||
|
* @param radius of the circle
|
||||||
|
* @param counter decides where the point is
|
||||||
|
* @return random point in the circle
|
||||||
|
*/
|
||||||
|
public static CoordI2 getReproduciblePointOnCircle(final CoordI2 center, final int radius, final AtomicInteger counter) {
|
||||||
|
final double angle = counter.incrementAndGet() / 3.0 % Math.PI * 2.0 - Math.PI;
|
||||||
|
return MutableCoordD2
|
||||||
|
.create(Math.cos(angle), Math.sin(angle))
|
||||||
|
.multiply(radius)
|
||||||
|
.toImmutableCoordI2()
|
||||||
|
.add(center);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates edge required at c1 to block the path from c0 to c1. Swap both parameters to get edge required at c0 to block the path from c1 to c0.<br/>
|
||||||
|
* <br/>
|
||||||
|
* c0==> |c1<br/>
|
||||||
|
* c0| <==c1<br/>
|
||||||
|
*
|
||||||
|
* @param c0 Position
|
||||||
|
* @param c1 Target
|
||||||
|
* @return Edges that are required as bit mask
|
||||||
|
*/
|
||||||
|
public static int calcEdgeRequirement(final CoordI2 c0, final CoordI2 c1) {
|
||||||
|
final int dx = c0.getX() - c1.getX();
|
||||||
|
final int dy = c0.getY() - c1.getY();
|
||||||
|
|
||||||
|
int edgeReq = 0;
|
||||||
|
if (dx < 0 && dy < 0) {
|
||||||
|
edgeReq = Edge2.LEFT.getBit() | Edge2.TOP.getBit();
|
||||||
|
}
|
||||||
|
else if (dx < 0 && dy == 0) {
|
||||||
|
edgeReq = Edge2.LEFT.getBit();
|
||||||
|
}
|
||||||
|
else if (dx < 0 && dy > 0) {
|
||||||
|
edgeReq = Edge2.LEFT.getBit() | Edge2.BOTTOM.getBit();
|
||||||
|
}
|
||||||
|
else if (dx == 0 && dy < 0) {
|
||||||
|
edgeReq = Edge2.TOP.getBit();
|
||||||
|
}
|
||||||
|
else if (dx == 0 && dy > 0) {
|
||||||
|
edgeReq = Edge2.BOTTOM.getBit();
|
||||||
|
}
|
||||||
|
else if (dx > 0 && dy < 0) {
|
||||||
|
edgeReq = Edge2.RIGHT.getBit() | Edge2.TOP.getBit();
|
||||||
|
}
|
||||||
|
else if (dx > 0 && dy == 0) {
|
||||||
|
edgeReq = Edge2.RIGHT.getBit();
|
||||||
|
}
|
||||||
|
else if (dx > 0 && dy > 0) {
|
||||||
|
edgeReq = Edge2.RIGHT.getBit() | Edge2.BOTTOM.getBit();
|
||||||
|
}
|
||||||
|
|
||||||
|
return edgeReq;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class DistanceAndSector {
|
||||||
|
|
||||||
|
double dist;
|
||||||
|
|
||||||
|
int sector;
|
||||||
|
|
||||||
|
public DistanceAndSector(final double dist, final int sector) {
|
||||||
|
this.dist = dist;
|
||||||
|
this.sector = sector;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,195 @@
|
|||||||
|
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||||
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||||
|
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package de.extio.game_engine.spatial2.index;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.ConcurrentModificationException;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import de.extio.game_engine.spatial2.model.CoordI2;
|
||||||
|
import de.extio.game_engine.spatial2.model.HasPosition2;
|
||||||
|
import de.extio.game_engine.spatial2.model.MutableCoordI2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements a grid index that can query objects with dimension 1 very fast at the expense of memory consumption. Use QuadTree instead for very large grids or 2 dimensional objects.
|
||||||
|
*/
|
||||||
|
public final class GridIndex2D<T extends HasPosition2> implements Iterable<T>, AutoCloseable {
|
||||||
|
|
||||||
|
private final List<List<List<T>>> index = new ArrayList<>();
|
||||||
|
|
||||||
|
private long modCnt;
|
||||||
|
|
||||||
|
public GridIndex2D() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(final T obj) {
|
||||||
|
this.ensureBounds(obj.getPosition());
|
||||||
|
|
||||||
|
this.index.get(obj.getPosition().getX()).get(obj.getPosition().getY()).add(obj);
|
||||||
|
this.modCnt++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addAll(final Iterable<T> objs) {
|
||||||
|
objs.forEach(this::add);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addAll(final Iterator<T> it) {
|
||||||
|
it.forEachRemaining(this::add);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void remove(final T obj) {
|
||||||
|
if (!this.checkBounds(obj.getPosition())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.removeInternal(obj);
|
||||||
|
this.modCnt++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<T> get(final HasPosition2 hasPosition2) {
|
||||||
|
final CoordI2 coord = hasPosition2.getPosition();
|
||||||
|
if (!this.checkBounds(coord)) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.index.get(coord.getX()).get(coord.getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears but keeps internal state allocated
|
||||||
|
*/
|
||||||
|
public void clear() {
|
||||||
|
this.index.forEach(list -> list.forEach(List::clear));
|
||||||
|
this.modCnt++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears and frees internal state
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
this.index.clear();
|
||||||
|
this.modCnt = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<T> iterator() {
|
||||||
|
return new Iterator<>() {
|
||||||
|
|
||||||
|
int x;
|
||||||
|
|
||||||
|
int y;
|
||||||
|
|
||||||
|
int i;
|
||||||
|
|
||||||
|
long modCnt;
|
||||||
|
|
||||||
|
{
|
||||||
|
this.modCnt = GridIndex2D.this.modCnt;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
return this.x < GridIndex2D.this.index.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T next() {
|
||||||
|
if (this.modCnt != GridIndex2D.this.modCnt) {
|
||||||
|
throw new ConcurrentModificationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
final T result = GridIndex2D.this.index.get(this.x).get(this.y).get(this.i);
|
||||||
|
this.forward();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove() {
|
||||||
|
GridIndex2D.this.removeInternal(GridIndex2D.this.index.get(this.x).get(this.y).get(this.i));
|
||||||
|
if (!this.validate()) {
|
||||||
|
this.forward();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void forward() {
|
||||||
|
while (this.hasNext()) {
|
||||||
|
if (++this.i >= GridIndex2D.this.index.get(this.x).get(this.y).size()) {
|
||||||
|
this.i = 0;
|
||||||
|
if (++this.y >= GridIndex2D.this.index.get(this.x).size()) {
|
||||||
|
this.y = 0;
|
||||||
|
this.x++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.validate()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean validate() {
|
||||||
|
return this.x < GridIndex2D.this.index.size() && this.y < GridIndex2D.this.index.get(this.x).size() && this.i < GridIndex2D.this.index.get(this.x).get(this.y).size();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void forEach(final Consumer<? super T> action) {
|
||||||
|
for (int x = 0; x < this.index.size(); x++) {
|
||||||
|
for (int y = 0; y < this.index.get(x).size(); y++) {
|
||||||
|
for (int i = 0; i < this.index.get(x).get(y).size(); i++) {
|
||||||
|
action.accept(this.index.get(x).get(y).get(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void forEach(final BiConsumer<CoordI2, List<T>> action) {
|
||||||
|
final CoordI2 coord = MutableCoordI2.create();
|
||||||
|
|
||||||
|
for (int x = 0; x < this.index.size(); x++) {
|
||||||
|
for (int y = 0; y < this.index.get(x).size(); y++) {
|
||||||
|
if (this.index.get(x).get(y).size() > 0) {
|
||||||
|
coord.setXY(x, y);
|
||||||
|
action.accept(coord, this.index.get(x).get(y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureBounds(final CoordI2 coord) {
|
||||||
|
int delta = this.index.size() - coord.getX();
|
||||||
|
if (delta < 1) {
|
||||||
|
for (delta = -delta; delta >= 0; delta--) {
|
||||||
|
this.index.add(new ArrayList<>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delta = this.index.get(coord.getX()).size() - coord.getY();
|
||||||
|
if (delta < 1) {
|
||||||
|
for (delta = -delta; delta >= 0; delta--) {
|
||||||
|
this.index.get(coord.getX()).add(new ArrayList<>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean checkBounds(final CoordI2 coord) {
|
||||||
|
return coord.getX() >= 0 && coord.getY() >= 0 && coord.getX() < this.index.size() && coord.getY() < this.index.get(coord.getX()).size();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeInternal(final T obj) {
|
||||||
|
this.index.get(obj.getPosition().getX()).get(obj.getPosition().getY()).remove(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||||
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||||
|
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package de.extio.game_engine.spatial2.index;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
import de.extio.game_engine.spatial2.WorldUtils2;
|
||||||
|
import de.extio.game_engine.spatial2.model.CoordI2;
|
||||||
|
import de.extio.game_engine.spatial2.model.ImmutableCoordI2;
|
||||||
|
import de.extio.game_engine.spatial2.model.SpatialIndex2Capable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LinearSearchIndex2D doing linear search only. Use it only for very special use cases. Implementation is mainly intended for performance comparison.
|
||||||
|
*/
|
||||||
|
public final class LinearSearchIndex2D<T extends SpatialIndex2Capable> implements SpatialIndex2D<T> {
|
||||||
|
|
||||||
|
private static final int RESULT_DIM_START = 8;
|
||||||
|
|
||||||
|
private static final int RESULT_DIM_MAX = 96;
|
||||||
|
|
||||||
|
private List<T> objects = new ArrayList<>();
|
||||||
|
|
||||||
|
private int resultDim = RESULT_DIM_START;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<T> iterator() {
|
||||||
|
return this.objects.iterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void add(final T obj) {
|
||||||
|
this.objects.add(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove(final T obj) {
|
||||||
|
this.objects.remove(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void move(final T obj, final CoordI2 oldPosition, final CoordI2 oldDimension) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<T> find(final CoordI2 position, final CoordI2 dimension, final Predicate<T> filter) {
|
||||||
|
List<T> result = null;
|
||||||
|
|
||||||
|
for (int i = 0; i < this.objects.size(); i++) {
|
||||||
|
final T obj = this.objects.get(i);
|
||||||
|
|
||||||
|
if (WorldUtils2.intersects(position, dimension, obj.getPosition(), obj.getDimension())) {
|
||||||
|
if (filter == null || filter.test(obj)) {
|
||||||
|
if (result == null) {
|
||||||
|
result = new ArrayList<>(dimension.equals(ImmutableCoordI2.one()) ? RESULT_DIM_START : this.resultDim);
|
||||||
|
}
|
||||||
|
result.add(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result == null) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
if (result.size() > this.resultDim) {
|
||||||
|
this.resultDim = Math.min(result.size(), RESULT_DIM_MAX);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T findFirstAt(final CoordI2 position, final Predicate<T> filter) {
|
||||||
|
for (int i = 0; i < this.objects.size(); i++) {
|
||||||
|
final T obj = this.objects.get(i);
|
||||||
|
|
||||||
|
if (WorldUtils2.intersects(position, ImmutableCoordI2.one(), obj.getPosition(), obj.getDimension())) {
|
||||||
|
if (filter == null || filter.test(obj)) {
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void executeAt(final CoordI2 position, final Consumer<T> consumer, final Predicate<T> filter) {
|
||||||
|
for (int i = 0; i < this.objects.size(); i++) {
|
||||||
|
final T obj = this.objects.get(i);
|
||||||
|
|
||||||
|
if (WorldUtils2.intersects(position, ImmutableCoordI2.one(), obj.getPosition(), obj.getDimension())) {
|
||||||
|
if (filter == null || filter.test(obj)) {
|
||||||
|
consumer.accept(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
this.objects = new ArrayList<>();
|
||||||
|
this.resultDim = RESULT_DIM_START;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
this.objects.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
293
src/main/java/de/extio/game_engine/spatial2/index/Matrix2.java
Normal file
293
src/main/java/de/extio/game_engine/spatial2/index/Matrix2.java
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||||
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||||
|
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package de.extio.game_engine.spatial2.index;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.ConcurrentModificationException;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import de.extio.game_engine.spatial2.model.CoordI2;
|
||||||
|
import de.extio.game_engine.spatial2.model.Edge2;
|
||||||
|
import de.extio.game_engine.spatial2.model.HasPosition2;
|
||||||
|
import de.extio.game_engine.spatial2.model.ImmutableCoordI2;
|
||||||
|
import de.extio.game_engine.spatial2.model.MutableCoordI2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements a 2D matrix
|
||||||
|
*/
|
||||||
|
public final class Matrix2<T> implements Iterable<T> {
|
||||||
|
|
||||||
|
private List<List<T>> matrix = new ArrayList<>();
|
||||||
|
|
||||||
|
private final CoordI2 dimension = MutableCoordI2.create();
|
||||||
|
|
||||||
|
private long modCnt;
|
||||||
|
|
||||||
|
public Matrix2() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public Matrix2(final int x, final int y) {
|
||||||
|
this.allocate(Math.max(0, x - 1), Math.max(0, y - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Matrix2(final Matrix2<T> other) {
|
||||||
|
this.dimension.setX(other.matrix.size());
|
||||||
|
|
||||||
|
this.matrix = new ArrayList<>(other.matrix);
|
||||||
|
for (int x = 0; x < this.matrix.size(); x++) {
|
||||||
|
final List<T> column = other.matrix.get(x);
|
||||||
|
this.matrix.set(x, new ArrayList<>(column));
|
||||||
|
|
||||||
|
if (column.size() > this.dimension.getY()) {
|
||||||
|
this.dimension.setY(column.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears and frees internal state
|
||||||
|
*/
|
||||||
|
public void close() {
|
||||||
|
this.matrix.clear();
|
||||||
|
this.dimension.setXY(0, 0);
|
||||||
|
this.modCnt++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears but keeps internal state allocated
|
||||||
|
*/
|
||||||
|
public void clear() {
|
||||||
|
this.matrix.forEach(col -> col.replaceAll((t) -> null));
|
||||||
|
this.modCnt++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void put(final HasPosition2 hasPosition, final T obj) {
|
||||||
|
this.put(hasPosition.getPosition().getX(), hasPosition.getPosition().getY(), obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void put(final int x, final int y, final T obj) {
|
||||||
|
if (x < 0 || y < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.allocate(x, y);
|
||||||
|
|
||||||
|
this.matrix.get(x).set(y, obj);
|
||||||
|
this.modCnt++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void remove(final HasPosition2 hasPosition) {
|
||||||
|
this.remove(hasPosition.getPosition().getX(), hasPosition.getPosition().getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void remove(final int x, final int y) {
|
||||||
|
if (!this.checkBounds(x, y)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.matrix.get(x).set(y, null);
|
||||||
|
this.modCnt++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeAll() {
|
||||||
|
for (int x = 0; x < this.matrix.size(); x++) {
|
||||||
|
for (int y = 0; y < this.matrix.get(x).size(); y++) {
|
||||||
|
this.matrix.get(x).set(y, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.modCnt++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T get(final HasPosition2 hasPosition) {
|
||||||
|
return this.get(hasPosition.getPosition().getX(), hasPosition.getPosition().getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
public T get(final int x, final int y) {
|
||||||
|
if (!this.checkBounds(x, y)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.matrix.get(x).get(y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean contains(final HasPosition2 hasPosition) {
|
||||||
|
return this.contains(hasPosition.getPosition().getX(), hasPosition.getPosition().getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean contains(final int x, final int y) {
|
||||||
|
return this.checkBounds(x, y) && this.matrix.get(x).get(y) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// x and y are indices, not the dimension or size!
|
||||||
|
public void allocate(final int x, final int y) {
|
||||||
|
if (x < 0 || y < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int delta = this.matrix.size() - x;
|
||||||
|
if (delta < 1) {
|
||||||
|
for (delta = -delta; delta >= 0; delta--) {
|
||||||
|
final List<T> row = new ArrayList<>(y + 1);
|
||||||
|
for (int i = 0; i <= y; i++) {
|
||||||
|
row.add(null);
|
||||||
|
}
|
||||||
|
this.matrix.add(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (x >= this.dimension.getX()) {
|
||||||
|
this.dimension.setX(x + 1);
|
||||||
|
}
|
||||||
|
if (y >= this.dimension.getY()) {
|
||||||
|
this.dimension.setY(y + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delta = this.matrix.get(x).size() - y;
|
||||||
|
if (delta < 1) {
|
||||||
|
for (delta = -delta; delta >= 0; delta--) {
|
||||||
|
this.matrix.get(x).add(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (y >= this.dimension.getY()) {
|
||||||
|
this.dimension.setY(y + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void rotate(final Edge2 direction) {
|
||||||
|
final List<List<T>> original = new ArrayList<>(this.matrix);
|
||||||
|
final CoordI2 origDim = ImmutableCoordI2.create(this.dimension);
|
||||||
|
|
||||||
|
this.close();
|
||||||
|
this.allocate(origDim.getY() - 1, origDim.getX() - 1);
|
||||||
|
|
||||||
|
final CoordI2 posSrc = MutableCoordI2.create();
|
||||||
|
for (int x = 0; x < origDim.getY(); x++) {
|
||||||
|
for (int y = 0; y < origDim.getX(); y++) {
|
||||||
|
if (direction == Edge2.LEFT) {
|
||||||
|
posSrc.setXY(origDim.getX() - y - 1, x);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
posSrc.setXY(y, origDim.getY() - x - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<T> originalObjs = original.get(posSrc.getX());
|
||||||
|
if (originalObjs.size() > posSrc.getY()) {
|
||||||
|
this.matrix.get(x).set(y, originalObjs.get(posSrc.getY()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<T> iterator() {
|
||||||
|
return new Iterator<>() {
|
||||||
|
|
||||||
|
private int x = 0;
|
||||||
|
|
||||||
|
private int y = 0;
|
||||||
|
|
||||||
|
private T obj = null;
|
||||||
|
|
||||||
|
private long modCnt;
|
||||||
|
|
||||||
|
{
|
||||||
|
this.modCnt = Matrix2.this.modCnt;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
return this.findNext(false) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T next() {
|
||||||
|
try {
|
||||||
|
if (this.modCnt != Matrix2.this.modCnt) {
|
||||||
|
throw new ConcurrentModificationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.findNext(true);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
this.obj = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private T findNext(final boolean nseExc) {
|
||||||
|
if (this.obj != null) {
|
||||||
|
return this.obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
int x = this.x;
|
||||||
|
int y = this.y;
|
||||||
|
for (; x < Matrix2.this.matrix.size(); x++) {
|
||||||
|
for (; y < Matrix2.this.matrix.get(x).size(); y++) {
|
||||||
|
this.obj = Matrix2.this.matrix.get(x).get(y);
|
||||||
|
if (this.obj != null) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y + 1;
|
||||||
|
if (this.y >= Matrix2.this.matrix.get(x).size()) {
|
||||||
|
this.y = 0;
|
||||||
|
this.x++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.obj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
y = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nseExc) {
|
||||||
|
throw new NoSuchElementException();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void forEach(final Consumer<? super T> action) {
|
||||||
|
for (int x = 0; x < this.matrix.size(); x++) {
|
||||||
|
for (int y = 0; y < this.matrix.get(x).size(); y++) {
|
||||||
|
final T obj = this.matrix.get(x).get(y);
|
||||||
|
if (obj != null) {
|
||||||
|
action.accept(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void forEach(final BiConsumer<CoordI2, T> consumer) {
|
||||||
|
final CoordI2 coord = MutableCoordI2.create();
|
||||||
|
|
||||||
|
for (int x = 0; x < this.matrix.size(); x++) {
|
||||||
|
for (int y = 0; y < this.matrix.get(x).size(); y++) {
|
||||||
|
final T obj = this.matrix.get(x).get(y);
|
||||||
|
if (obj != null) {
|
||||||
|
coord.setXY(x, y);
|
||||||
|
consumer.accept(coord, obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public CoordI2 getDimension() {
|
||||||
|
return ImmutableCoordI2.create(this.dimension);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean checkBounds(final int x, final int y) {
|
||||||
|
return x >= 0 && y >= 0 && x < this.matrix.size() && y < this.matrix.get(x).size();
|
||||||
|
}
|
||||||
|
}
|
||||||
733
src/main/java/de/extio/game_engine/spatial2/index/QuadTree.java
Normal file
733
src/main/java/de/extio/game_engine/spatial2/index/QuadTree.java
Normal file
@@ -0,0 +1,733 @@
|
|||||||
|
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||||
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||||
|
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package de.extio.game_engine.spatial2.index;
|
||||||
|
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Deque;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
import de.extio.game_engine.spatial2.model.CoordI2;
|
||||||
|
import de.extio.game_engine.spatial2.model.ImmutableCoordI2;
|
||||||
|
import de.extio.game_engine.spatial2.model.MutableCoordI2;
|
||||||
|
import de.extio.game_engine.spatial2.model.RectI2;
|
||||||
|
import de.extio.game_engine.spatial2.model.SpatialIndex2Capable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the preferred SpatialIndex2D implementation to index bigger amounts of spatial data and query them efficiently.
|
||||||
|
* You can consider using GridIndex2D instead for small grids with one-dimensional objects only.
|
||||||
|
* A quadtree is a tree data structure in which each internal node has exactly four children. Quadtrees are the two-dimensional analog of octrees and are most often used to partition a two-dimensional space by recursively subdividing it into four quadrants or regions.
|
||||||
|
*/
|
||||||
|
public final class QuadTree<T extends SpatialIndex2Capable> implements SpatialIndex2D<T> {
|
||||||
|
|
||||||
|
private static final int ORDER = 4;
|
||||||
|
|
||||||
|
private static final int DEPTH_START = 8;
|
||||||
|
|
||||||
|
private static final int RESULT_DIM_START = 6;
|
||||||
|
|
||||||
|
private static final int RESULT_DIM_MAX = 96;
|
||||||
|
|
||||||
|
private Node<T> root;
|
||||||
|
|
||||||
|
private int depth;
|
||||||
|
|
||||||
|
private int[] traverseStack;
|
||||||
|
|
||||||
|
private Node<T>[] nodesCache;
|
||||||
|
|
||||||
|
private int resultDim = RESULT_DIM_START;
|
||||||
|
|
||||||
|
private final RectI2 rectCached = new RectI2(MutableCoordI2.create(), MutableCoordI2.create());
|
||||||
|
|
||||||
|
private final AtomicReference<List<T>> resultHolder = new AtomicReference<>();
|
||||||
|
|
||||||
|
public QuadTree() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
public void add(final T obj) {
|
||||||
|
final RectI2 rect = this.toRectI2(obj.getPosition(), obj.getDimension());
|
||||||
|
this.expandTree(rect);
|
||||||
|
|
||||||
|
Node<T> node = this.root;
|
||||||
|
traversal: while (true) {
|
||||||
|
if (node.branches == null) {
|
||||||
|
// Add leaf to fresh branch
|
||||||
|
this.addLeave(node, obj);
|
||||||
|
if (node.leaves.size() < ORDER) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overflow -> Split if not already at lowest level
|
||||||
|
if (node.createBranches()) {
|
||||||
|
final List<T> toSplit = node.leaves;
|
||||||
|
if (toSplit != null) {
|
||||||
|
node.leaves = null;
|
||||||
|
|
||||||
|
splitLeaves: for (int l = 0; l < toSplit.size(); l++) {
|
||||||
|
final T leaf = toSplit.get(l);
|
||||||
|
|
||||||
|
for (int b = 0; b < ORDER; b++) {
|
||||||
|
if (this.contains(node.branches[b], leaf)) {
|
||||||
|
this.addLeave(node.branches[b], leaf);
|
||||||
|
continue splitLeaves;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.addLeave(node, leaf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Traverse
|
||||||
|
for (int i = 0; i < ORDER; i++) {
|
||||||
|
if (this.contains(node.branches[i], rect)) {
|
||||||
|
node = node.branches[i];
|
||||||
|
continue traversal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Node does not fit into branches -> add to current level
|
||||||
|
this.addLeave(node, obj);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private void expandTree(final RectI2 rect) {
|
||||||
|
if (this.root == null) {
|
||||||
|
final int side = 1 << DEPTH_START;
|
||||||
|
this.root = new Node<>(rect.getC0().getX() - side, rect.getC0().getY() - side, side << 1);
|
||||||
|
this.root.createBranches();
|
||||||
|
this.recalcDepth();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; !this.contains(this.root, rect); i++) {
|
||||||
|
final RectI2 expansion = new RectI2(MutableCoordI2.create(), MutableCoordI2.create());
|
||||||
|
final int[] branchesIntersect = new int[4];
|
||||||
|
int intersects = 0;
|
||||||
|
int containsBranch = -1;
|
||||||
|
|
||||||
|
expansion.getC0().setXY(this.root.xMin, this.root.yMin);
|
||||||
|
expansion.getC1().setXY(Integer.MAX_VALUE, Integer.MAX_VALUE);
|
||||||
|
if (this.contains(expansion, rect)) {
|
||||||
|
containsBranch = 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (this.intersects(expansion, rect)) {
|
||||||
|
branchesIntersect[intersects++] = 0;
|
||||||
|
}
|
||||||
|
expansion.getC0().setXY(Integer.MIN_VALUE, this.root.yMin);
|
||||||
|
expansion.getC1().setXY(this.root.xMax, Integer.MAX_VALUE);
|
||||||
|
if (this.contains(expansion, rect)) {
|
||||||
|
containsBranch = 2;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (this.intersects(expansion, rect)) {
|
||||||
|
branchesIntersect[intersects++] = 2;
|
||||||
|
}
|
||||||
|
expansion.getC0().setXY(Integer.MIN_VALUE, Integer.MIN_VALUE);
|
||||||
|
expansion.getC1().setXY(this.root.xMax, this.root.yMax);
|
||||||
|
if (this.contains(expansion, rect)) {
|
||||||
|
containsBranch = 3;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (this.intersects(expansion, rect)) {
|
||||||
|
branchesIntersect[intersects++] = 3;
|
||||||
|
}
|
||||||
|
expansion.getC0().setXY(this.root.xMin, Integer.MIN_VALUE);
|
||||||
|
expansion.getC1().setXY(Integer.MAX_VALUE, this.root.yMax);
|
||||||
|
if (this.contains(expansion, rect)) {
|
||||||
|
containsBranch = 1;
|
||||||
|
}
|
||||||
|
else if (this.intersects(expansion, rect)) {
|
||||||
|
branchesIntersect[intersects++] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Node<T> rootNew = null;
|
||||||
|
final int branch = containsBranch != -1 ? containsBranch : branchesIntersect[i % intersects];
|
||||||
|
final int size = this.root.xMax - this.root.xMin + 1;
|
||||||
|
switch (branch) {
|
||||||
|
case 0:
|
||||||
|
rootNew = new Node<>(this.root.xMin, this.root.yMin, size << 1);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
rootNew = new Node<>(this.root.xMin, this.root.yMin - size, size << 1);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
rootNew = new Node<>(this.root.xMin - size, this.root.yMin, size << 1);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
rootNew = new Node<>(this.root.xMin - size, this.root.yMin - size, size << 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
rootNew.createBranches();
|
||||||
|
rootNew.branches[branch] = this.root;
|
||||||
|
rootNew.branches[branch].parent = rootNew;
|
||||||
|
|
||||||
|
this.root = rootNew;
|
||||||
|
this.recalcDepth();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alternative approach, center around the origin. In principle very elegant but the tree always expands to all directions whereas the data may only expand to a specific direction
|
||||||
|
// while (!this.contains(this.root, rect)) {
|
||||||
|
// final int side = this.root.xMax - this.root.xMin + 1;
|
||||||
|
// final Node<T> rootNew = new Node<>(this.root.xMin - (side >> 1), this.root.yMin - (side >> 1), side << 1);
|
||||||
|
// rootNew.createBranches();
|
||||||
|
// for (int i = 0; i < ORDER; i++) {
|
||||||
|
// rootNew.branches[i].createBranches();
|
||||||
|
// }
|
||||||
|
// rootNew.branches[0].branches[3] = this.root.branches[0];
|
||||||
|
// rootNew.branches[0].branches[3].parent = rootNew.branches[0];
|
||||||
|
// rootNew.branches[1].branches[2] = this.root.branches[1];
|
||||||
|
// rootNew.branches[1].branches[2].parent = rootNew.branches[1];
|
||||||
|
// rootNew.branches[2].branches[1] = this.root.branches[2];
|
||||||
|
// rootNew.branches[2].branches[1].parent = rootNew.branches[2];
|
||||||
|
// rootNew.branches[3].branches[0] = this.root.branches[3];
|
||||||
|
// rootNew.branches[3].branches[0].parent = rootNew.branches[3];
|
||||||
|
// rootNew.leaves = this.root.leaves;
|
||||||
|
//
|
||||||
|
// this.root = rootNew;
|
||||||
|
// this.recalcDepth();
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private void recalcDepth() {
|
||||||
|
this.depth = Integer.numberOfTrailingZeros(Integer.highestOneBit(this.root.xMax - this.root.xMin)) + 1;
|
||||||
|
this.traverseStack = new int[this.depth];
|
||||||
|
this.nodesCache = new Node[this.depth];
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addLeave(final Node<T> node, final T obj) {
|
||||||
|
if (node.leaves == null) {
|
||||||
|
node.leaves = new ArrayList<>();
|
||||||
|
}
|
||||||
|
node.leaves.add(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove(final T obj) {
|
||||||
|
final RectI2 rect = this.toRectI2Bounded(obj.getPosition(), obj.getDimension());
|
||||||
|
if (rect == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.remove(obj, rect);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private void remove(final T obj, final RectI2 rect) {
|
||||||
|
Node<T> node = this.root;
|
||||||
|
traversal: while (true) {
|
||||||
|
if (node.leaves != null) {
|
||||||
|
node.leaves.remove(obj);
|
||||||
|
if (node.leaves.isEmpty()) {
|
||||||
|
node.leaves = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.shrink(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.branches != null) {
|
||||||
|
for (int i = 0; i < ORDER; i++) {
|
||||||
|
if (this.contains(node.branches[i], rect)) {
|
||||||
|
node = node.branches[i];
|
||||||
|
continue traversal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void shrink(final Node<T> node_) {
|
||||||
|
Node<T> node = node_;
|
||||||
|
while (node.parent != null) {
|
||||||
|
for (int i = 0; i < ORDER; i++) {
|
||||||
|
if (node.parent.branches[i].leaves != null || node.parent.branches[i].branches != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
node.parent.branches = null;
|
||||||
|
node = node.parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
public void move(final T obj, final CoordI2 oldPosition, final CoordI2 oldDimension) {
|
||||||
|
final RectI2 rect = this.toRectI2Bounded(oldPosition, oldDimension);
|
||||||
|
if (rect != null) {
|
||||||
|
int nodes = 0;
|
||||||
|
Node<T> node = this.root;
|
||||||
|
traversal: while (true) {
|
||||||
|
if (node.leaves != null) {
|
||||||
|
this.nodesCache[nodes++] = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.branches != null) {
|
||||||
|
for (int i = 0; i < ORDER; i++) {
|
||||||
|
if (this.contains(node.branches[i], rect)) {
|
||||||
|
node = node.branches[i];
|
||||||
|
continue traversal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.contains(node, obj)) {
|
||||||
|
while (nodes-- > 0) {
|
||||||
|
final Node<T> curNode = this.nodesCache[nodes];
|
||||||
|
if (curNode.leaves != null) {
|
||||||
|
curNode.leaves.remove(obj);
|
||||||
|
if (curNode.leaves.isEmpty()) {
|
||||||
|
curNode.leaves = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.shrink(node);
|
||||||
|
|
||||||
|
this.add(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.add(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<T> find(final CoordI2 position, final CoordI2 dimension, final Predicate<T> filter) {
|
||||||
|
if (dimension.equals(ImmutableCoordI2.one())) {
|
||||||
|
return this.findDim1(position, filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
final RectI2 rect = this.toRectI2Bounded(position, dimension);
|
||||||
|
if (rect == null) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.findDimN(rect, filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<T> findDim1(final CoordI2 position, final Predicate<T> filter) {
|
||||||
|
this.resultHolder.set(null);
|
||||||
|
|
||||||
|
this.executeAt(position, obj -> {
|
||||||
|
if (this.resultHolder.get() == null) {
|
||||||
|
this.resultHolder.set(new ArrayList<>(RESULT_DIM_START));
|
||||||
|
}
|
||||||
|
this.resultHolder.get().add(obj);
|
||||||
|
}, filter);
|
||||||
|
|
||||||
|
final List<T> result = this.resultHolder.get();
|
||||||
|
if (result == null) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
this.resultHolder.set(null);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private List<T> findDimN(final RectI2 rect, final Predicate<T> filter) {
|
||||||
|
List<T> result = null;
|
||||||
|
|
||||||
|
int level = 0;
|
||||||
|
int branch = 0;
|
||||||
|
Node<T> node = this.root;
|
||||||
|
|
||||||
|
traversal: while (true) {
|
||||||
|
if (node.branches != null) {
|
||||||
|
for (int b = branch; b < ORDER; b++) {
|
||||||
|
if (this.intersects(node.branches[b], rect)) {
|
||||||
|
node = node.branches[b];
|
||||||
|
this.traverseStack[level++] = b + 1;
|
||||||
|
branch = 0;
|
||||||
|
|
||||||
|
continue traversal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.leaves != null) {
|
||||||
|
for (int i = 0; i < node.leaves.size(); i++) {
|
||||||
|
final T obj = node.leaves.get(i);
|
||||||
|
if (this.intersects(obj, rect) && (filter == null || filter.test((T) obj))) {
|
||||||
|
if (result == null) {
|
||||||
|
result = new ArrayList<>(this.resultDim);
|
||||||
|
}
|
||||||
|
result.add(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.parent == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
node = node.parent;
|
||||||
|
branch = this.traverseStack[--level];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result == null) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
if (result.size() > this.resultDim) {
|
||||||
|
this.resultDim = Math.min(result.size(), RESULT_DIM_MAX);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
public T findFirstAt(final CoordI2 position, final Predicate<T> filter) {
|
||||||
|
final RectI2 rect = this.toRectI2Bounded(position, ImmutableCoordI2.one());
|
||||||
|
if (rect == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Node<T> node = this.root;
|
||||||
|
traversal: while (true) {
|
||||||
|
if (node.leaves != null) {
|
||||||
|
for (int i = 0; i < node.leaves.size(); i++) {
|
||||||
|
final T obj = node.leaves.get(i);
|
||||||
|
if (this.contains(obj, rect) && (filter == null || filter.test((T) obj))) {
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.branches != null) {
|
||||||
|
for (int i = 0; i < ORDER; i++) {
|
||||||
|
if (this.contains(node.branches[i], rect)) {
|
||||||
|
node = node.branches[i];
|
||||||
|
continue traversal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
public void executeAt(final CoordI2 position, final Consumer<T> consumer, final Predicate<T> filter) {
|
||||||
|
final RectI2 rect = this.toRectI2Bounded(position, ImmutableCoordI2.one());
|
||||||
|
if (rect == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Node<T> node = this.root;
|
||||||
|
traversal: while (true) {
|
||||||
|
if (node.leaves != null) {
|
||||||
|
for (int i = 0; i < node.leaves.size(); i++) {
|
||||||
|
final T obj = node.leaves.get(i);
|
||||||
|
if (this.contains(obj, rect) && (filter == null || filter.test((T) obj))) {
|
||||||
|
consumer.accept(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.branches != null) {
|
||||||
|
for (int i = 0; i < ORDER; i++) {
|
||||||
|
if (this.contains(node.branches[i], rect)) {
|
||||||
|
node = node.branches[i];
|
||||||
|
continue traversal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<T> iterator() {
|
||||||
|
return new Iterator<>() {
|
||||||
|
|
||||||
|
private final Deque<Integer> branchStack = new ArrayDeque<>(QuadTree.this.depth);
|
||||||
|
|
||||||
|
private T next;
|
||||||
|
|
||||||
|
private Node<T> node = QuadTree.this.root;
|
||||||
|
|
||||||
|
private int branch;
|
||||||
|
|
||||||
|
private int leaf;
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
if (this.next != null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
traversal: while (true) {
|
||||||
|
if (this.node.branches != null) {
|
||||||
|
if (this.branch < ORDER) {
|
||||||
|
this.node = this.node.branches[this.branch];
|
||||||
|
this.branchStack.addFirst(this.branch + 1);
|
||||||
|
this.branch = 0;
|
||||||
|
continue traversal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.node.leaves != null) {
|
||||||
|
if (this.leaf < this.node.leaves.size()) {
|
||||||
|
final T obj = this.node.leaves.get(this.leaf++);
|
||||||
|
this.next = obj;
|
||||||
|
break traversal;
|
||||||
|
}
|
||||||
|
this.leaf = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.node.parent == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
this.node = this.node.parent;
|
||||||
|
this.branch = this.branchStack.removeFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.next != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T next() {
|
||||||
|
try {
|
||||||
|
return this.next;
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
this.next = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove() {
|
||||||
|
if (this.leaf > 0 && this.leaf <= this.node.leaves.size()) {
|
||||||
|
this.node.leaves.remove(this.leaf-- - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void forEach(final Consumer<? super T> action) {
|
||||||
|
this.traverseAll(node -> {
|
||||||
|
if (node.leaves != null) {
|
||||||
|
for (int i = 0; i < node.leaves.size(); i++) {
|
||||||
|
final T obj = node.leaves.get(i);
|
||||||
|
action.accept(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
this.resultDim = RESULT_DIM_START;
|
||||||
|
this.traverseStack = null;
|
||||||
|
this.nodesCache = null;
|
||||||
|
this.root = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
this.traverseAll(node -> {
|
||||||
|
if (node.leaves != null) {
|
||||||
|
node.leaves = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private void traverseAll(final Consumer<Node<T>> consumer) {
|
||||||
|
if (this.root == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int level = 0;
|
||||||
|
int branch = 0;
|
||||||
|
Node<T> node = this.root;
|
||||||
|
|
||||||
|
traversal: while (true) {
|
||||||
|
if (node.branches != null && branch < ORDER) {
|
||||||
|
node = node.branches[branch];
|
||||||
|
this.traverseStack[level++] = branch + 1;
|
||||||
|
branch = 0;
|
||||||
|
|
||||||
|
continue traversal;
|
||||||
|
}
|
||||||
|
|
||||||
|
consumer.accept(node);
|
||||||
|
|
||||||
|
if (node.parent == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
node = node.parent;
|
||||||
|
branch = this.traverseStack[--level];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private RectI2 toRectI2(final CoordI2 position, final CoordI2 dimension) {
|
||||||
|
this.rectCached.getC0().setXY(position.getX(), position.getY());
|
||||||
|
this.rectCached.getC1().setXY(position.getX() + dimension.getX() - 1, position.getY() + dimension.getY() - 1);
|
||||||
|
|
||||||
|
return this.rectCached;
|
||||||
|
}
|
||||||
|
|
||||||
|
private RectI2 toRectI2Bounded(final CoordI2 position, final CoordI2 dimension) {
|
||||||
|
if (this.root == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.rectCached.getC0().setXY(Math.max(position.getX(), this.root.xMin), Math.max(position.getY(), this.root.yMin));
|
||||||
|
this.rectCached.getC1().setXY(Math.min(position.getX() + dimension.getX() - 1, this.root.xMax), Math.min(position.getY() + dimension.getY() - 1, this.root.yMax));
|
||||||
|
|
||||||
|
if (this.rectCached.getC1().getX() - this.rectCached.getC0().getX() < 0 || this.rectCached.getC1().getY() - this.rectCached.getC0().getY() < 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return this.rectCached;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean contains(final Node<T> node, final RectI2 rect) {
|
||||||
|
return rect.getC0().getX() >= node.xMin &&
|
||||||
|
rect.getC0().getY() >= node.yMin &&
|
||||||
|
rect.getC1().getX() <= node.xMax &&
|
||||||
|
rect.getC1().getY() <= node.yMax;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean contains(final Node<T> node, final T obj) {
|
||||||
|
return obj.getPosition().getX() >= node.xMin &&
|
||||||
|
obj.getPosition().getY() >= node.yMin &&
|
||||||
|
obj.getPosition().getX() + obj.getDimension().getX() - 1 <= node.xMax &&
|
||||||
|
obj.getPosition().getY() + obj.getDimension().getY() - 1 <= node.yMax;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean contains(final T obj, final RectI2 rect) {
|
||||||
|
return rect.getC0().getX() >= obj.getPosition().getX() &&
|
||||||
|
rect.getC0().getY() >= obj.getPosition().getY() &&
|
||||||
|
rect.getC1().getX() <= obj.getPosition().getX() + obj.getDimension().getX() - 1 &&
|
||||||
|
rect.getC1().getY() <= obj.getPosition().getY() + obj.getDimension().getY() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean contains(final RectI2 rect0, final RectI2 rect1) {
|
||||||
|
return rect1.getC0().getX() >= rect0.getC0().getX() &&
|
||||||
|
rect1.getC0().getY() >= rect0.getC0().getY() &&
|
||||||
|
rect1.getC1().getX() <= rect0.getC1().getX() &&
|
||||||
|
rect1.getC1().getY() <= rect0.getC1().getY();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean intersects(final Node<T> node, final RectI2 rect) {
|
||||||
|
return ((node.xMax + 1 < node.xMin || node.xMax >= rect.getC0().getX()) &&
|
||||||
|
(node.yMax + 1 < node.yMin || node.yMax >= rect.getC0().getY()) &&
|
||||||
|
(rect.getC1().getX() + 1 < rect.getC0().getX() || rect.getC1().getX() >= node.xMin) &&
|
||||||
|
(rect.getC1().getY() + 1 < rect.getC0().getY() || rect.getC1().getY() >= node.yMin));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean intersects(final T obj, final RectI2 rect) {
|
||||||
|
final int oUpperX = obj.getDimension().getX() + obj.getPosition().getX();
|
||||||
|
final int oUpperY = obj.getDimension().getY() + obj.getPosition().getY();
|
||||||
|
return ((oUpperX < obj.getPosition().getX() || oUpperX > rect.getC0().getX()) &&
|
||||||
|
(oUpperY < obj.getPosition().getY() || oUpperY > rect.getC0().getY()) &&
|
||||||
|
(rect.getC1().getX() + 1 < rect.getC0().getX() || rect.getC1().getX() >= obj.getPosition().getX()) &&
|
||||||
|
(rect.getC1().getY() + 1 < rect.getC0().getY() || rect.getC1().getY() >= obj.getPosition().getY()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean intersects(final RectI2 rect0, final RectI2 rect1) {
|
||||||
|
return ((rect0.getC1().getX() + 1 < rect0.getC0().getX() || rect0.getC1().getX() >= rect1.getC0().getX()) &&
|
||||||
|
(rect0.getC1().getY() + 1 < rect0.getC0().getY() || rect0.getC1().getY() >= rect1.getC0().getY()) &&
|
||||||
|
(rect1.getC1().getX() + 1 < rect1.getC0().getX() || rect1.getC1().getX() >= rect0.getC0().getX()) &&
|
||||||
|
(rect1.getC1().getY() + 1 < rect1.getC0().getY() || rect1.getC1().getY() >= rect0.getC0().getY()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Node<T extends SpatialIndex2Capable> {
|
||||||
|
|
||||||
|
int xMin;
|
||||||
|
|
||||||
|
int xMax;
|
||||||
|
|
||||||
|
int yMin;
|
||||||
|
|
||||||
|
int yMax;
|
||||||
|
|
||||||
|
List<T> leaves;
|
||||||
|
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
Node[] branches;
|
||||||
|
|
||||||
|
Node<T> parent;
|
||||||
|
|
||||||
|
Node(final int xMin, final int yMin, final int size) {
|
||||||
|
this.xMin = xMin;
|
||||||
|
this.xMax = xMin + size - 1;
|
||||||
|
this.yMin = yMin;
|
||||||
|
this.yMax = yMin + size - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Node(final int xMin, final int xMax, final int yMin, final int yMax, final Node<T> parent) {
|
||||||
|
this.xMin = xMin;
|
||||||
|
this.xMax = xMax;
|
||||||
|
this.yMin = yMin;
|
||||||
|
this.yMax = yMax;
|
||||||
|
this.parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean createBranches() {
|
||||||
|
final int partition = (this.xMax - this.xMin + 1) >> 1;
|
||||||
|
if (partition == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.branches = new Node[ORDER];
|
||||||
|
this.branches[0] = new Node<>(this.xMin, this.xMin + partition - 1, this.yMin, this.yMin + partition - 1, this);
|
||||||
|
this.branches[1] = new Node<>(this.xMin, this.xMin + partition - 1, this.yMin + partition, this.yMax, this);
|
||||||
|
this.branches[2] = new Node<>(this.xMin + partition, this.xMax, this.yMin, this.yMin + partition - 1, this);
|
||||||
|
this.branches[3] = new Node<>(this.xMin + partition, this.xMax, this.yMin + partition, this.yMax, this);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
final StringBuilder builder = new StringBuilder();
|
||||||
|
builder.append("Node [xMin=");
|
||||||
|
builder.append(this.xMin);
|
||||||
|
builder.append(", xMax=");
|
||||||
|
builder.append(this.xMax);
|
||||||
|
builder.append(", yMin=");
|
||||||
|
builder.append(this.yMin);
|
||||||
|
builder.append(", yMax=");
|
||||||
|
builder.append(this.yMax);
|
||||||
|
builder.append("]");
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||||
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||||
|
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package de.extio.game_engine.spatial2.index;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
import de.extio.game_engine.spatial2.model.CoordI2;
|
||||||
|
import de.extio.game_engine.spatial2.model.HasPositionAndDimension2;
|
||||||
|
import de.extio.game_engine.spatial2.model.ImmutableCoordI2;
|
||||||
|
import de.extio.game_engine.spatial2.model.RectI2;
|
||||||
|
import de.extio.game_engine.spatial2.model.SpatialIndex2Capable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spatial indexes are used for spatial queries. See also QuadTree
|
||||||
|
*/
|
||||||
|
public interface SpatialIndex2D<T extends SpatialIndex2Capable> extends Iterable<T>, AutoCloseable {
|
||||||
|
|
||||||
|
static AtomicLong SESSION = new AtomicLong(1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an object to the index
|
||||||
|
*/
|
||||||
|
void add(T obj);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes an object from the index
|
||||||
|
*/
|
||||||
|
void remove(T obj);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves an indexed object to a new location. Implementations are usually faster than doing separate remove() and add() operations
|
||||||
|
*/
|
||||||
|
void move(T obj, CoordI2 oldPosition, CoordI2 oldDimension);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Queries all object intersecting with a given area
|
||||||
|
*/
|
||||||
|
default Collection<T> find(final RectI2 rect) {
|
||||||
|
return this.find(rect.getC0(), ImmutableCoordI2.create(rect.getC1()).substract(rect.getC0()), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Queries all object intersecting with a given area
|
||||||
|
*/
|
||||||
|
default List<T> find(final HasPositionAndDimension2 hasPositionAndDimension2) {
|
||||||
|
return this.find(hasPositionAndDimension2.getPosition(), hasPositionAndDimension2.getDimension(), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Queries all object intersecting with a given area
|
||||||
|
*/
|
||||||
|
default List<T> find(final CoordI2 position, final CoordI2 dimension) {
|
||||||
|
return this.find(position, dimension, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Queries all object intersecting with a given area. Objects can be pre-filtered which is usually faster than returning them first and then filter in your own code.
|
||||||
|
*/
|
||||||
|
List<T> find(CoordI2 position, CoordI2 dimension, Predicate<T> filter);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the first object at a given position
|
||||||
|
*/
|
||||||
|
default T findFirstAt(final CoordI2 position) {
|
||||||
|
return this.findFirstAt(position, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the first object at a given position. Objects can be pre-filtered which is usually faster than returning them first and then filter in your own code.
|
||||||
|
*/
|
||||||
|
T findFirstAt(CoordI2 position, Predicate<T> filter);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes a consumer for all objects at a given position.
|
||||||
|
*/
|
||||||
|
default void executeAt(final CoordI2 position, final Consumer<T> consumer) {
|
||||||
|
this.executeAt(position, consumer, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes a consumer for all objects at a given position. Objects can be pre-filtered which is usually faster than returning them first and then filter in your own code.
|
||||||
|
*/
|
||||||
|
void executeAt(CoordI2 position, Consumer<T> consumer, Predicate<T> filter);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears and frees internal state
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
void close();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears but keeps internal state allocated. Optimization for re-use.
|
||||||
|
*/
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
}
|
||||||
92
src/main/java/de/extio/game_engine/spatial2/model/Area2.java
Normal file
92
src/main/java/de/extio/game_engine/spatial2/model/Area2.java
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||||
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||||
|
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package de.extio.game_engine.spatial2.model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A 2D area, having a position (top left corner) and a dimension
|
||||||
|
*/
|
||||||
|
public class Area2 extends Point2 implements HasPositionAndDimension2, SpatialIndex2Capable {
|
||||||
|
|
||||||
|
private CoordI2 dimension;
|
||||||
|
|
||||||
|
private transient long bsi2Session;
|
||||||
|
|
||||||
|
public Area2() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Area2(final CoordI2 coord, final CoordI2 dimension) {
|
||||||
|
super(coord);
|
||||||
|
this.setDimension(dimension);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Area2(final HasPositionAndDimension2 other) {
|
||||||
|
super(ImmutableCoordI2.create(other.getPosition()));
|
||||||
|
this.setDimension(ImmutableCoordI2.create(other.getDimension()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CoordI2 getDimension() {
|
||||||
|
return this.dimension;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDimension(final CoordI2 dimension) {
|
||||||
|
this.dimension = dimension;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isInSpatialIndex2ResultSet(final long session) {
|
||||||
|
return this.bsi2Session == session;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void includeInSpatialIndex2ResultSet(final long session) {
|
||||||
|
this.bsi2Session = session;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
final int prime = 31;
|
||||||
|
int result = super.hashCode();
|
||||||
|
result = prime * result + ((this.dimension == null) ? 0 : this.dimension.hashCode());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!super.equals(obj)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final Area2 other = (Area2) obj;
|
||||||
|
if (this.dimension == null) {
|
||||||
|
if (other.dimension != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!this.dimension.equals(other.dimension)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
final StringBuilder builder = new StringBuilder();
|
||||||
|
builder.append("Area2 [getPosition()=");
|
||||||
|
builder.append(this.getPosition());
|
||||||
|
builder.append(", dimension=");
|
||||||
|
builder.append(this.dimension);
|
||||||
|
builder.append("]");
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||||
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||||
|
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package de.extio.game_engine.spatial2.model;
|
||||||
|
|
||||||
|
import de.extio.game_engine.spatial2.WorldUtils2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to convert between mutable and immutable representations of tuples (coordinates and vectors)
|
||||||
|
*/
|
||||||
|
public interface Coord2Conversion {
|
||||||
|
|
||||||
|
default CoordI2 toMutableCoordI2() {
|
||||||
|
if (this instanceof CoordI2) {
|
||||||
|
return MutableCoordI2.create((CoordI2) this);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return MutableCoordI2.create((CoordD2) this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
default CoordI2 toImmutableCoordI2() {
|
||||||
|
if (this instanceof CoordI2) {
|
||||||
|
return ImmutableCoordI2.create((CoordI2) this);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return ImmutableCoordI2.create((CoordD2) this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
default CoordD2 toMutableCoordD2() {
|
||||||
|
if (this instanceof CoordI2) {
|
||||||
|
return MutableCoordD2.create((CoordI2) this);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return MutableCoordD2.create((CoordD2) this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
default CoordD2 toImmutableCoordD2() {
|
||||||
|
if (this instanceof CoordI2) {
|
||||||
|
return ImmutableCoordD2.create((CoordI2) this);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return ImmutableCoordD2.create((CoordD2) this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a vector to a unit normal vector
|
||||||
|
*/
|
||||||
|
default CoordD2 toVNorm() {
|
||||||
|
CoordD2 vNorm;
|
||||||
|
|
||||||
|
if (this instanceof ImmutableCoordI2) {
|
||||||
|
vNorm = ImmutableCoordD2.create((ImmutableCoordI2) this);
|
||||||
|
}
|
||||||
|
else if (this instanceof MutableCoordI2) {
|
||||||
|
vNorm = MutableCoordD2.create((MutableCoordI2) this);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
vNorm = (CoordD2) this;
|
||||||
|
}
|
||||||
|
|
||||||
|
final double length = WorldUtils2.getDistance(vNorm);
|
||||||
|
if (length > 0.0) {
|
||||||
|
vNorm = vNorm.divide(length);
|
||||||
|
}
|
||||||
|
|
||||||
|
return vNorm;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||||
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||||
|
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package de.extio.game_engine.spatial2.model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A floating point number tuple (can represent a point, a coordinate or a vector in 2D space).
|
||||||
|
*/
|
||||||
|
public interface CoordD2 extends Comparable<CoordD2>, Coord2Conversion {
|
||||||
|
|
||||||
|
double getX();
|
||||||
|
|
||||||
|
void setX(final double x);
|
||||||
|
|
||||||
|
double getY();
|
||||||
|
|
||||||
|
void setY(final double y);
|
||||||
|
|
||||||
|
CoordD2 setXY(final CoordD2 other);
|
||||||
|
|
||||||
|
CoordD2 setXY(final double x, final double y);
|
||||||
|
|
||||||
|
CoordD2 add(final CoordD2 delta);
|
||||||
|
|
||||||
|
CoordD2 add(final double dx, final double dy);
|
||||||
|
|
||||||
|
CoordD2 add(double n);
|
||||||
|
|
||||||
|
CoordD2 substract(final CoordD2 delta);
|
||||||
|
|
||||||
|
CoordD2 substract(final double dx, final double dy);
|
||||||
|
|
||||||
|
CoordD2 substract(double n);
|
||||||
|
|
||||||
|
CoordD2 multiply(final CoordD2 delta);
|
||||||
|
|
||||||
|
CoordD2 multiply(final double dx, final double dy);
|
||||||
|
|
||||||
|
CoordD2 multiply(final double n);
|
||||||
|
|
||||||
|
CoordD2 divide(final CoordD2 delta);
|
||||||
|
|
||||||
|
CoordD2 divide(final double dx, final double dy);
|
||||||
|
|
||||||
|
CoordD2 divide(final double n);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int compareTo(final CoordD2 other) {
|
||||||
|
return this.getY() == other.getY() ? Double.compare(this.getX(), other.getX()) : Double.compare(this.getY(), other.getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||||
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||||
|
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package de.extio.game_engine.spatial2.model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An integer tuple (can represent a point, a coordinate or a vector in 2D space).
|
||||||
|
*/
|
||||||
|
public interface CoordI2 extends HasPosition2, Comparable<CoordI2>, Coord2Conversion {
|
||||||
|
|
||||||
|
int getX();
|
||||||
|
|
||||||
|
void setX(final int x);
|
||||||
|
|
||||||
|
int getY();
|
||||||
|
|
||||||
|
void setY(final int y);
|
||||||
|
|
||||||
|
CoordI2 setXY(final HasPosition2 other);
|
||||||
|
|
||||||
|
CoordI2 setXY(final int x, final int y);
|
||||||
|
|
||||||
|
CoordI2 add(final HasPosition2 delta);
|
||||||
|
|
||||||
|
CoordI2 add(final int dx, final int dy);
|
||||||
|
|
||||||
|
CoordI2 add(final int n);
|
||||||
|
|
||||||
|
CoordI2 substract(final HasPosition2 delta);
|
||||||
|
|
||||||
|
CoordI2 substract(final int dx, final int dy);
|
||||||
|
|
||||||
|
CoordI2 substract(final int n);
|
||||||
|
|
||||||
|
CoordI2 multiply(final HasPosition2 delta);
|
||||||
|
|
||||||
|
CoordI2 multiply(final int dx, final int dy);
|
||||||
|
|
||||||
|
CoordI2 multiply(final int n);
|
||||||
|
|
||||||
|
CoordI2 divide(final HasPosition2 delta);
|
||||||
|
|
||||||
|
CoordI2 divide(final int dx, final int dy);
|
||||||
|
|
||||||
|
CoordI2 divide(final int n);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default CoordI2 getPosition() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
default int compareTo(final CoordI2 other) {
|
||||||
|
return this.getY() == other.getY() ? Integer.compare(this.getX(), other.getX()) : Integer.compare(this.getY(), other.getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
156
src/main/java/de/extio/game_engine/spatial2/model/Edge2.java
Normal file
156
src/main/java/de/extio/game_engine/spatial2/model/Edge2.java
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||||
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||||
|
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package de.extio.game_engine.spatial2.model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identifies a vertex or a side
|
||||||
|
*/
|
||||||
|
public enum Edge2 {
|
||||||
|
|
||||||
|
RIGHT(),
|
||||||
|
BOTTOM(),
|
||||||
|
LEFT(),
|
||||||
|
TOP();
|
||||||
|
|
||||||
|
public final static Edge2[] VALUES_CACHED = new Edge2[] {
|
||||||
|
RIGHT,
|
||||||
|
BOTTOM,
|
||||||
|
LEFT,
|
||||||
|
TOP
|
||||||
|
};
|
||||||
|
|
||||||
|
private final static CoordD2 VD_BOTTOM = MutableCoordD2.create(0.0, 1.0);
|
||||||
|
|
||||||
|
private final static CoordD2 VD_LEFT = MutableCoordD2.create(-1.0, 0.0);
|
||||||
|
|
||||||
|
private final static CoordD2 VD_RIGHT = MutableCoordD2.create(1.0, 0.0);
|
||||||
|
|
||||||
|
private final static CoordD2 VD_TOP = MutableCoordD2.create(0.0, -1.0);
|
||||||
|
|
||||||
|
private final static CoordI2 VI_BOTTOM = ImmutableCoordI2.create(0, 1);
|
||||||
|
|
||||||
|
private final static CoordI2 VI_LEFT = ImmutableCoordI2.create(-1, 0);
|
||||||
|
|
||||||
|
private final static CoordI2 VI_RIGHT = ImmutableCoordI2.create(1, 0);
|
||||||
|
|
||||||
|
private final static CoordI2 VI_TOP = ImmutableCoordI2.create(0, -1);
|
||||||
|
|
||||||
|
public static Edge2 fromBit(final int bit) {
|
||||||
|
if ((bit & RIGHT.bit) == RIGHT.bit) {
|
||||||
|
return RIGHT;
|
||||||
|
}
|
||||||
|
else if ((bit & BOTTOM.bit) == BOTTOM.bit) {
|
||||||
|
return BOTTOM;
|
||||||
|
}
|
||||||
|
else if ((bit & LEFT.bit) == LEFT.bit) {
|
||||||
|
return LEFT;
|
||||||
|
}
|
||||||
|
else if ((bit & TOP.bit) == TOP.bit) {
|
||||||
|
return TOP;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rotates edge from RIGHT to destination clock-wise. BOTTOM rotates by 1 stop, LEFT by 2 and TOP by 3.
|
||||||
|
* @param dest stops
|
||||||
|
* @return Rotated edge2
|
||||||
|
*/
|
||||||
|
public Edge2 rotate(final Edge2 dest) {
|
||||||
|
return Edge2.fromBit(Edge2.rotate(this.getBit(), dest));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rotates edges represented by bit mask (Edge2::getBit)
|
||||||
|
* @param edges Bit mask of edges
|
||||||
|
* @param dest Rotate from RIGHT to destination clock-wise. BOTTOM rotates by 1 stop, LEFT by 2 and TOP by 3.
|
||||||
|
* @return Rotated bit mask
|
||||||
|
*/
|
||||||
|
public static int rotate(final int edges, final Edge2 dest) {
|
||||||
|
// Formula:
|
||||||
|
// bits: Number of significant bits, in our case 4 (4 edges)
|
||||||
|
// shift: rotate by n steps
|
||||||
|
//
|
||||||
|
// ((x << shift) | (x >> (bits - shift))) & ((1 << bits) - 1)
|
||||||
|
// |----- Circular left shift ----| |-- Cut significant bits --|
|
||||||
|
|
||||||
|
if (dest == null) {
|
||||||
|
return edges;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ((edges << Integer.numberOfTrailingZeros(dest.bit)) | (edges >> (Edge2.VALUES_CACHED.length - Integer.numberOfTrailingZeros(dest.bit)))) & ((1 << Edge2.VALUES_CACHED.length) - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a vector with length 1 pointing to the direction edge2 representates
|
||||||
|
* @param edge2
|
||||||
|
* @return Vector
|
||||||
|
*/
|
||||||
|
public static CoordD2 toVectorD(final Edge2 edge2) {
|
||||||
|
switch (edge2) {
|
||||||
|
case BOTTOM:
|
||||||
|
return VD_BOTTOM;
|
||||||
|
case LEFT:
|
||||||
|
return VD_LEFT;
|
||||||
|
case RIGHT:
|
||||||
|
return VD_RIGHT;
|
||||||
|
case TOP:
|
||||||
|
return VD_TOP;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a vector with length 1 pointing to the direction edge2 representates
|
||||||
|
* @param edge2
|
||||||
|
* @return Vector
|
||||||
|
*/
|
||||||
|
public static CoordI2 toVectorI(final Edge2 edge2) {
|
||||||
|
switch (edge2) {
|
||||||
|
case BOTTOM:
|
||||||
|
return VI_BOTTOM;
|
||||||
|
case LEFT:
|
||||||
|
return VI_LEFT;
|
||||||
|
case RIGHT:
|
||||||
|
return VI_RIGHT;
|
||||||
|
case TOP:
|
||||||
|
return VI_TOP;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String toString(final int edges) {
|
||||||
|
final StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("Edges [");
|
||||||
|
|
||||||
|
for (final Edge2 edge : Edge2.VALUES_CACHED) {
|
||||||
|
if ((edges & (1 << edge.ordinal())) != 0) {
|
||||||
|
sb.append(edge.name());
|
||||||
|
sb.append(" ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append("]");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int bit;
|
||||||
|
|
||||||
|
Edge2() {
|
||||||
|
this.bit = 1 << this.ordinal();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBit() {
|
||||||
|
return this.bit;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||||
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||||
|
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package de.extio.game_engine.spatial2.model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for objects having a position (top left corner)
|
||||||
|
*/
|
||||||
|
public interface HasPosition2 {
|
||||||
|
|
||||||
|
CoordI2 getPosition();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||||
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||||
|
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package de.extio.game_engine.spatial2.model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for objects occupying 2D area, having a position (top left corner) and a dimension
|
||||||
|
*/
|
||||||
|
public interface HasPositionAndDimension2 extends HasPosition2 {
|
||||||
|
|
||||||
|
CoordI2 getDimension();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,192 @@
|
|||||||
|
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||||
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||||
|
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package de.extio.game_engine.spatial2.model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immutable implementation of CoordD2
|
||||||
|
*/
|
||||||
|
public final class ImmutableCoordD2 implements CoordD2 {
|
||||||
|
|
||||||
|
private final static ImmutableCoordD2 COORD_ZERO = new ImmutableCoordD2(0, 0);
|
||||||
|
|
||||||
|
private final static ImmutableCoordD2 COORD_ONE = new ImmutableCoordD2(1, 1);
|
||||||
|
|
||||||
|
public static ImmutableCoordD2 zero() {
|
||||||
|
return COORD_ZERO;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ImmutableCoordD2 one() {
|
||||||
|
return COORD_ONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ImmutableCoordD2 create() {
|
||||||
|
return COORD_ZERO;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ImmutableCoordD2 create(final CoordD2 other) {
|
||||||
|
if (other == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (other instanceof ImmutableCoordD2) {
|
||||||
|
return (ImmutableCoordD2) other;
|
||||||
|
}
|
||||||
|
return new ImmutableCoordD2(other.getX(), other.getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ImmutableCoordD2 create(final double x, final double y) {
|
||||||
|
return new ImmutableCoordD2(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ImmutableCoordD2 create(final HasPosition2 hasPosition2) {
|
||||||
|
return new ImmutableCoordD2(hasPosition2.getPosition().getX(), hasPosition2.getPosition().getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
private double x;
|
||||||
|
|
||||||
|
private double y;
|
||||||
|
|
||||||
|
ImmutableCoordD2() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ImmutableCoordD2(final double x, final double y) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getX() {
|
||||||
|
return this.x;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setX(final double x) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getY() {
|
||||||
|
return this.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setY(final double y) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImmutableCoordD2 setXY(final CoordD2 other) {
|
||||||
|
return create(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImmutableCoordD2 setXY(final double x, final double y) {
|
||||||
|
return create(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImmutableCoordD2 add(final CoordD2 delta) {
|
||||||
|
return create(this.x + delta.getX(), this.y + delta.getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImmutableCoordD2 add(final double dx, final double dy) {
|
||||||
|
return create(this.x + dx, this.y + dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImmutableCoordD2 add(final double n) {
|
||||||
|
return create(this.x + n, this.y + n);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImmutableCoordD2 substract(final CoordD2 delta) {
|
||||||
|
return create(this.x - delta.getX(), this.y - delta.getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImmutableCoordD2 substract(final double dx, final double dy) {
|
||||||
|
return create(this.x - dx, this.y - dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImmutableCoordD2 substract(final double n) {
|
||||||
|
return create(this.x - n, this.y - n);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImmutableCoordD2 multiply(final CoordD2 delta) {
|
||||||
|
return create(this.x * delta.getX(), this.y * delta.getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImmutableCoordD2 multiply(final double dx, final double dy) {
|
||||||
|
return create(this.x * dx, this.y * dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CoordD2 multiply(final double n) {
|
||||||
|
return create(this.x * n, this.y * n);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImmutableCoordD2 divide(final CoordD2 delta) {
|
||||||
|
final double dx = delta.getX() != 0.0 ? delta.getX() : 1.0;
|
||||||
|
final double dy = delta.getY() != 0.0 ? delta.getY() : 1.0;
|
||||||
|
return create(this.x / dx, this.y / dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImmutableCoordD2 divide(final double dx, final double dy) {
|
||||||
|
final double dx_ = dx != 0.0 ? dx : 1.0;
|
||||||
|
final double dy_ = dy != 0.0 ? dy : 1.0;
|
||||||
|
return create(this.x / dx_, this.y / dy_);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CoordD2 divide(final double n) {
|
||||||
|
final double n_ = n != 0.0 ? n : 1.0;
|
||||||
|
return create(this.x / n_, this.y / n_);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
final int prime = 31;
|
||||||
|
int result = 1;
|
||||||
|
long temp;
|
||||||
|
temp = Double.doubleToLongBits(this.x);
|
||||||
|
result = prime * result + (int) (temp ^ (temp >>> 32));
|
||||||
|
temp = Double.doubleToLongBits(this.y);
|
||||||
|
result = prime * result + (int) (temp ^ (temp >>> 32));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if ((obj == null) || !(obj instanceof CoordD2)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final CoordD2 other = (CoordD2) obj;
|
||||||
|
if (Double.doubleToLongBits(this.x) != Double.doubleToLongBits(other.getX())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (Double.doubleToLongBits(this.y) != Double.doubleToLongBits(other.getY())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "D2i[" + this.x + ", " + this.y + "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,220 @@
|
|||||||
|
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||||
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||||
|
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package de.extio.game_engine.spatial2.model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immutable implementation of CoordI2
|
||||||
|
*/
|
||||||
|
public final class ImmutableCoordI2 implements CoordI2 {
|
||||||
|
|
||||||
|
private final static int COORD_CACHE_DIM = 100;
|
||||||
|
|
||||||
|
private final static int COORD_CACHE_BOUND = 50;
|
||||||
|
|
||||||
|
private final static int COORD_CACHE_SIZE = COORD_CACHE_DIM * COORD_CACHE_DIM;
|
||||||
|
|
||||||
|
private final static ImmutableCoordI2[] COORD_CACHE = new ImmutableCoordI2[COORD_CACHE_SIZE];
|
||||||
|
|
||||||
|
private volatile static ImmutableCoordI2 LAST_CACHED = new ImmutableCoordI2(0, 0);
|
||||||
|
|
||||||
|
private final static ImmutableCoordI2 COORD_ZERO = new ImmutableCoordI2(0, 0);
|
||||||
|
|
||||||
|
private final static ImmutableCoordI2 COORD_ONE = new ImmutableCoordI2(1, 1);
|
||||||
|
|
||||||
|
static {
|
||||||
|
int i = 0;
|
||||||
|
for (int x = -COORD_CACHE_BOUND; x < COORD_CACHE_BOUND; x++) {
|
||||||
|
for (int y = -COORD_CACHE_BOUND; y < COORD_CACHE_BOUND; y++) {
|
||||||
|
COORD_CACHE[i++] = new ImmutableCoordI2(x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ImmutableCoordI2 zero() {
|
||||||
|
return COORD_ZERO;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ImmutableCoordI2 one() {
|
||||||
|
return COORD_ONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ImmutableCoordI2 create() {
|
||||||
|
return COORD_ZERO;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ImmutableCoordI2 create(final HasPosition2 other) {
|
||||||
|
if (other == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (other instanceof ImmutableCoordI2) {
|
||||||
|
return (ImmutableCoordI2) other;
|
||||||
|
}
|
||||||
|
return ImmutableCoordI2.create(other.getPosition().getX(), other.getPosition().getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ImmutableCoordI2 create(final int x, final int y) {
|
||||||
|
if (x >= -COORD_CACHE_BOUND && x < COORD_CACHE_BOUND && y >= -COORD_CACHE_BOUND && y < COORD_CACHE_BOUND) {
|
||||||
|
final int idx = (x + COORD_CACHE_BOUND) * COORD_CACHE_DIM + (y + COORD_CACHE_BOUND);
|
||||||
|
if (idx >= 0 && idx < COORD_CACHE_SIZE) {
|
||||||
|
return COORD_CACHE[idx];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImmutableCoordI2 cached = LAST_CACHED;
|
||||||
|
if (cached.getX() != x || cached.getY() != y) {
|
||||||
|
cached = new ImmutableCoordI2(x, y);
|
||||||
|
LAST_CACHED = cached;
|
||||||
|
}
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ImmutableCoordI2 create(final CoordD2 other) {
|
||||||
|
return ImmutableCoordI2.create((int) other.getX(), (int) other.getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
private int x;
|
||||||
|
|
||||||
|
private int y;
|
||||||
|
|
||||||
|
ImmutableCoordI2() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ImmutableCoordI2(final int x, final int y) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getX() {
|
||||||
|
return this.x;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setX(final int x) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getY() {
|
||||||
|
return this.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setY(final int y) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImmutableCoordI2 setXY(final HasPosition2 other) {
|
||||||
|
return create(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImmutableCoordI2 setXY(final int x, final int y) {
|
||||||
|
return create(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImmutableCoordI2 add(final HasPosition2 delta) {
|
||||||
|
return create(this.x + delta.getPosition().getX(), this.y + delta.getPosition().getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImmutableCoordI2 add(final int dx, final int dy) {
|
||||||
|
return create(this.x + dx, this.y + dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImmutableCoordI2 add(final int n) {
|
||||||
|
return create(this.x + n, this.y + n);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImmutableCoordI2 substract(final HasPosition2 delta) {
|
||||||
|
return create(this.x - delta.getPosition().getX(), this.y - delta.getPosition().getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImmutableCoordI2 substract(final int dx, final int dy) {
|
||||||
|
return create(this.x - dx, this.y - dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImmutableCoordI2 substract(final int n) {
|
||||||
|
return create(this.x - n, this.y - n);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImmutableCoordI2 multiply(final HasPosition2 delta) {
|
||||||
|
return create(this.x * delta.getPosition().getX(), this.y * delta.getPosition().getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImmutableCoordI2 multiply(final int dx, final int dy) {
|
||||||
|
return create(this.x * dx, this.y * dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImmutableCoordI2 multiply(final int n) {
|
||||||
|
return create(this.x * n, this.y * n);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImmutableCoordI2 divide(final HasPosition2 delta) {
|
||||||
|
final int dx = delta.getPosition().getX() != 0 ? delta.getPosition().getX() : 1;
|
||||||
|
final int dy = delta.getPosition().getY() != 0 ? delta.getPosition().getY() : 1;
|
||||||
|
return create(this.x / dx, this.y / dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImmutableCoordI2 divide(final int dx, final int dy) {
|
||||||
|
final int dx_ = dx != 0 ? dx : 1;
|
||||||
|
final int dy_ = dy != 0 ? dy : 1;
|
||||||
|
return create(this.x / dx_, this.y / dy_);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImmutableCoordI2 divide(final int n) {
|
||||||
|
final int n_ = n != 0 ? n : 1;
|
||||||
|
return create(this.x / n_, this.y / n_);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
final int prime = 31;
|
||||||
|
int result = 1;
|
||||||
|
result = prime * result + this.x;
|
||||||
|
result = prime * result + this.y;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if ((obj == null) || !(obj instanceof CoordI2)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final CoordI2 other = (CoordI2) obj;
|
||||||
|
if (this.x != other.getX()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this.y != other.getY()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "I2i[" + this.x + ", " + this.y + "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,222 @@
|
|||||||
|
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||||
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||||
|
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package de.extio.game_engine.spatial2.model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immutable implementation of CoordD2
|
||||||
|
*/
|
||||||
|
public final class MutableCoordD2 implements CoordD2 {
|
||||||
|
|
||||||
|
public static MutableCoordD2 create() {
|
||||||
|
return new MutableCoordD2(0.0, 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MutableCoordD2 create(final double x, final double y) {
|
||||||
|
return new MutableCoordD2(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MutableCoordD2 create(final CoordD2 other) {
|
||||||
|
return new MutableCoordD2(other.getX(), other.getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MutableCoordD2 create(final HasPosition2 hasPosition2) {
|
||||||
|
return new MutableCoordD2(hasPosition2.getPosition().getX(), hasPosition2.getPosition().getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
private double x;
|
||||||
|
|
||||||
|
private double y;
|
||||||
|
|
||||||
|
MutableCoordD2() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
MutableCoordD2(final double x, final double y) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getX() {
|
||||||
|
return this.x;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setX(final double x) {
|
||||||
|
this.x = x;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getY() {
|
||||||
|
return this.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setY(final double y) {
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MutableCoordD2 setXY(final CoordD2 other) {
|
||||||
|
this.x = other.getX();
|
||||||
|
this.y = other.getY();
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MutableCoordD2 setXY(final double x, final double y) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MutableCoordD2 add(final CoordD2 delta) {
|
||||||
|
this.x += delta.getX();
|
||||||
|
this.y += delta.getY();
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MutableCoordD2 add(final double dx, final double dy) {
|
||||||
|
this.x += dx;
|
||||||
|
this.y += dy;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MutableCoordD2 add(final double n) {
|
||||||
|
this.x += n;
|
||||||
|
this.y += n;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MutableCoordD2 substract(final CoordD2 delta) {
|
||||||
|
this.x -= delta.getX();
|
||||||
|
this.y -= delta.getY();
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MutableCoordD2 substract(final double dx, final double dy) {
|
||||||
|
this.x -= dx;
|
||||||
|
this.y -= dy;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MutableCoordD2 substract(final double n) {
|
||||||
|
this.x -= n;
|
||||||
|
this.y -= n;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MutableCoordD2 multiply(final CoordD2 delta) {
|
||||||
|
this.x *= delta.getX();
|
||||||
|
this.y *= delta.getY();
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MutableCoordD2 multiply(final double dx, final double dy) {
|
||||||
|
this.x *= dx;
|
||||||
|
this.y *= dy;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CoordD2 multiply(final double n) {
|
||||||
|
this.x *= n;
|
||||||
|
this.y *= n;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MutableCoordD2 divide(final CoordD2 delta) {
|
||||||
|
if (delta.getX() != 0.0) {
|
||||||
|
this.x /= delta.getX();
|
||||||
|
}
|
||||||
|
if (delta.getY() != 0.0) {
|
||||||
|
this.y /= delta.getY();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MutableCoordD2 divide(final double dx, final double dy) {
|
||||||
|
if (dx != 0.0) {
|
||||||
|
this.x /= dx;
|
||||||
|
}
|
||||||
|
if (dy != 0.0) {
|
||||||
|
this.y /= dy;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CoordD2 divide(final double n) {
|
||||||
|
if (n != 0.0) {
|
||||||
|
this.x /= n;
|
||||||
|
this.y /= n;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
final int prime = 31;
|
||||||
|
int result = 1;
|
||||||
|
long temp;
|
||||||
|
temp = Double.doubleToLongBits(this.x);
|
||||||
|
result = prime * result + (int) (temp ^ (temp >>> 32));
|
||||||
|
temp = Double.doubleToLongBits(this.y);
|
||||||
|
result = prime * result + (int) (temp ^ (temp >>> 32));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if ((obj == null) || !(obj instanceof CoordD2)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final CoordD2 other = (CoordD2) obj;
|
||||||
|
if (Double.doubleToLongBits(this.x) != Double.doubleToLongBits(other.getX())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (Double.doubleToLongBits(this.y) != Double.doubleToLongBits(other.getY())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "D2m[" + this.x + ", " + this.y + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,219 @@
|
|||||||
|
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||||
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||||
|
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package de.extio.game_engine.spatial2.model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Immutable implementation of CoordI2
|
||||||
|
*/
|
||||||
|
public final class MutableCoordI2 implements CoordI2 {
|
||||||
|
|
||||||
|
public static MutableCoordI2 create() {
|
||||||
|
return new MutableCoordI2(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MutableCoordI2 create(final int x, final int y) {
|
||||||
|
return new MutableCoordI2(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MutableCoordI2 create(final HasPosition2 other) {
|
||||||
|
return new MutableCoordI2(other.getPosition().getX(), other.getPosition().getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MutableCoordI2 create(final CoordD2 other) {
|
||||||
|
return new MutableCoordI2((int) other.getX(), (int) other.getY());
|
||||||
|
}
|
||||||
|
|
||||||
|
private int x;
|
||||||
|
|
||||||
|
private int y;
|
||||||
|
|
||||||
|
MutableCoordI2() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
MutableCoordI2(final int x, final int y) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getX() {
|
||||||
|
return this.x;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setX(final int x) {
|
||||||
|
this.x = x;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getY() {
|
||||||
|
return this.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setY(final int y) {
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MutableCoordI2 setXY(final HasPosition2 other) {
|
||||||
|
this.x = other.getPosition().getX();
|
||||||
|
this.y = other.getPosition().getY();
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MutableCoordI2 setXY(final int x, final int y) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MutableCoordI2 add(final HasPosition2 delta) {
|
||||||
|
this.x += delta.getPosition().getX();
|
||||||
|
this.y += delta.getPosition().getY();
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MutableCoordI2 add(final int dx, final int dy) {
|
||||||
|
this.x += dx;
|
||||||
|
this.y += dy;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MutableCoordI2 add(final int n) {
|
||||||
|
this.x += n;
|
||||||
|
this.y += n;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MutableCoordI2 substract(final HasPosition2 delta) {
|
||||||
|
this.x -= delta.getPosition().getX();
|
||||||
|
this.y -= delta.getPosition().getY();
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MutableCoordI2 substract(final int dx, final int dy) {
|
||||||
|
this.x -= dx;
|
||||||
|
this.y -= dy;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MutableCoordI2 substract(final int n) {
|
||||||
|
this.x -= n;
|
||||||
|
this.y -= n;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MutableCoordI2 multiply(final HasPosition2 delta) {
|
||||||
|
this.x *= delta.getPosition().getX();
|
||||||
|
this.y *= delta.getPosition().getY();
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MutableCoordI2 multiply(final int dx, final int dy) {
|
||||||
|
this.x *= dx;
|
||||||
|
this.y *= dy;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MutableCoordI2 multiply(final int n) {
|
||||||
|
this.x *= n;
|
||||||
|
this.y *= n;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MutableCoordI2 divide(final HasPosition2 delta) {
|
||||||
|
if (delta.getPosition().getX() != 0) {
|
||||||
|
this.x /= delta.getPosition().getX();
|
||||||
|
}
|
||||||
|
if (delta.getPosition().getY() != 0) {
|
||||||
|
this.y /= delta.getPosition().getY();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MutableCoordI2 divide(final int dx, final int dy) {
|
||||||
|
if (dx != 0) {
|
||||||
|
this.x /= dx;
|
||||||
|
}
|
||||||
|
if (dy != 0) {
|
||||||
|
this.y /= dy;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MutableCoordI2 divide(final int n) {
|
||||||
|
if (n != 0) {
|
||||||
|
this.x /= n;
|
||||||
|
this.y /= n;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
final int prime = 31;
|
||||||
|
int result = 1;
|
||||||
|
result = prime * result + this.x;
|
||||||
|
result = prime * result + this.y;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if ((obj == null) || !(obj instanceof CoordI2)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final CoordI2 other = (CoordI2) obj;
|
||||||
|
if (this.x != other.getX()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this.y != other.getY()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "I2m[" + this.x + ", " + this.y + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||||
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||||
|
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package de.extio.game_engine.spatial2.model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A point in 2D space
|
||||||
|
*/
|
||||||
|
public class Point2 implements HasPosition2 {
|
||||||
|
|
||||||
|
private CoordI2 coord;
|
||||||
|
|
||||||
|
public Point2() {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
public Point2(final CoordI2 coord) {
|
||||||
|
this.coord = coord;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPosition(final CoordI2 coord) {
|
||||||
|
this.coord = coord;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CoordI2 getPosition() {
|
||||||
|
return this.coord;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
final int prime = 31;
|
||||||
|
int result = 1;
|
||||||
|
result = prime * result + ((this.coord == null) ? 0 : this.coord.hashCode());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if ((obj == null) || (this.getClass() != obj.getClass())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final Point2 other = (Point2) obj;
|
||||||
|
if (this.coord == null) {
|
||||||
|
if (other.coord != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!this.coord.equals(other.coord)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
final StringBuilder builder = new StringBuilder();
|
||||||
|
builder.append("Point2 [coord=");
|
||||||
|
builder.append(this.coord);
|
||||||
|
builder.append("]");
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
109
src/main/java/de/extio/game_engine/spatial2/model/RectI2.java
Normal file
109
src/main/java/de/extio/game_engine/spatial2/model/RectI2.java
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||||
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||||
|
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package de.extio.game_engine.spatial2.model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A rectangle represented by top left (c0) and bottom right (c1) vertices
|
||||||
|
*/
|
||||||
|
public class RectI2 {
|
||||||
|
|
||||||
|
private CoordI2 c0;
|
||||||
|
|
||||||
|
private CoordI2 c1;
|
||||||
|
|
||||||
|
public RectI2() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public RectI2(final CoordI2 c0, final CoordI2 c1) {
|
||||||
|
this.c0 = c0;
|
||||||
|
this.c1 = c1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RectI2(final RectI2 other) {
|
||||||
|
if (other == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (other.c0 != null) {
|
||||||
|
if (other.c0 instanceof ImmutableCoordI2) {
|
||||||
|
this.c0 = ImmutableCoordI2.create(other.c0);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.c0 = MutableCoordI2.create(other.c0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (other.c1 != null) {
|
||||||
|
if (other.c1 instanceof ImmutableCoordI2) {
|
||||||
|
this.c1 = ImmutableCoordI2.create(other.c1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.c1 = MutableCoordI2.create(other.c1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public CoordI2 getC0() {
|
||||||
|
return this.c0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setC0(final CoordI2 c0) {
|
||||||
|
this.c0 = c0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CoordI2 getC1() {
|
||||||
|
return this.c1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setC1(final CoordI2 c1) {
|
||||||
|
this.c1 = c1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
final int prime = 31;
|
||||||
|
int result = 1;
|
||||||
|
result = prime * result + ((this.c0 == null) ? 0 : this.c0.hashCode());
|
||||||
|
result = prime * result + ((this.c1 == null) ? 0 : this.c1.hashCode());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(final Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if ((obj == null) || (this.getClass() != obj.getClass())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final RectI2 other = (RectI2) obj;
|
||||||
|
if (this.c0 == null) {
|
||||||
|
if (other.c0 != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!this.c0.equals(other.c0)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (this.c1 == null) {
|
||||||
|
if (other.c1 != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!this.c1.equals(other.c1)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "RectI2 [c0=" + this.c0 + ", c1=" + this.c1 + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/* Copyright (C) 2023 Stephan Birkl - All Rights Reserved.
|
||||||
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||||
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||||
|
* OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package de.extio.game_engine.spatial2.model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Objects that can be indexed in SpatialIndex2D implementations
|
||||||
|
*/
|
||||||
|
public interface SpatialIndex2Capable extends HasPositionAndDimension2 {
|
||||||
|
|
||||||
|
boolean isInSpatialIndex2ResultSet(long session);
|
||||||
|
|
||||||
|
void includeInSpatialIndex2ResultSet(long session);
|
||||||
|
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user