diff --git a/src/main/java/xyz/valnet/hadean/gameobjects/ISelectable.java b/src/main/java/xyz/valnet/hadean/gameobjects/ISelectable.java new file mode 100644 index 0000000..85c8be0 --- /dev/null +++ b/src/main/java/xyz/valnet/hadean/gameobjects/ISelectable.java @@ -0,0 +1,7 @@ +package xyz.valnet.hadean.gameobjects; + +import xyz.valnet.engine.math.Vector4f; + +public interface ISelectable { + public Vector4f getWorldBox(); +} \ No newline at end of file diff --git a/src/main/java/xyz/valnet/hadean/gameobjects/ISelectionChangeListener.java b/src/main/java/xyz/valnet/hadean/gameobjects/ISelectionChangeListener.java new file mode 100644 index 0000000..352fb95 --- /dev/null +++ b/src/main/java/xyz/valnet/hadean/gameobjects/ISelectionChangeListener.java @@ -0,0 +1,7 @@ +package xyz.valnet.hadean.gameobjects; + +import java.util.List; + +public interface ISelectionChangeListener { + public void selectionChanged(List selected); +} diff --git a/src/main/java/xyz/valnet/hadean/gameobjects/Selection.java b/src/main/java/xyz/valnet/hadean/gameobjects/Selection.java new file mode 100644 index 0000000..77040e2 --- /dev/null +++ b/src/main/java/xyz/valnet/hadean/gameobjects/Selection.java @@ -0,0 +1,138 @@ +package xyz.valnet.hadean.gameobjects; + +import java.util.ArrayList; +import java.util.List; + +import xyz.valnet.engine.App; +import xyz.valnet.engine.math.Vector2f; +import xyz.valnet.engine.math.Vector4f; +import xyz.valnet.engine.scenegraph.GameObject; +import xyz.valnet.hadean.util.Assets; + +import static xyz.valnet.engine.util.Math.lerp; + +public class Selection extends GameObject { + + public Vector2f initialCoords; + private Camera camera; + private float animation = 0; + private float animationMax = 15; + private float animationAmplitude = 0.3f; + private List listeners = new ArrayList(); + + @Override + public void start() { + camera = get(Camera.class); + } + + public void subscribe(ISelectionChangeListener listener) { + listeners.add(listener); + } + + private float distance(Vector2f a, Vector2f b) { + return (float) Math.sqrt(Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2)); + } + + @Override + public void tick(float dTime) { + if(animation < animationMax) animation ++; + if(animation > animationMax) animation = animationMax; + + // TODO at some point, this will need to be blocked by other things on top. like a ui over the scene should make selections like, not happen?! + if(App.mouseLeft) { + Vector2f currentMouseCoords = new Vector2f(App.mouseX, App.mouseY); + if(initialCoords == null) { + initialCoords = currentMouseCoords; + } + } else { + if(initialCoords != null) { + + makeSelection(new Vector4f( + initialCoords.x, + initialCoords.y, + App.mouseX, + App.mouseY + )); + + initialCoords = null; + } + } + } + + @Override + public void render() { + + float t = animation / animationMax; + float p = lerp(animationAmplitude, 0, t); + + for(ISelectable thing : selected) { + Vector4f box = thing.getWorldBox(); + Vector2f min = camera.world2screen(box.x - p, box.y - p); + Vector2f max = camera.world2screen(box.z + p, box.w + p); + Assets.selectedFrame.draw((int)min.x, (int)min.y, (int)(max.x - min.x), (int)(max.y - min.y)); + } + + if(initialCoords != null) { + Assets.selectionFrame.draw((int) initialCoords.x, (int) initialCoords.y, (int) (App.mouseX - initialCoords.x), (int) (App.mouseY - initialCoords.y)); + } + } + + // this will take any x1, y1, x2, y2 vector and make x1 < x2 and y1 < y2; + private Vector4f sortVector(Vector4f vector) { + return new Vector4f( + Math.min(vector.x, vector.z), + Math.min(vector.y, vector.w), + Math.max(vector.x, vector.z), + Math.max(vector.y, vector.w) + ); + } + + private List selected = new ArrayList(); + + private void makeSelection(Vector4f a) { + selected.clear(); + Vector4f normalizedSelectionBoxScreen = sortVector(a); + Vector2f selectionBoxWorldMin = camera.screen2world(normalizedSelectionBoxScreen.x, normalizedSelectionBoxScreen.y); + Vector2f selectionBoxWorldMax = camera.screen2world(normalizedSelectionBoxScreen.z, normalizedSelectionBoxScreen.w); + + List selectables = getAll(ISelectable.class); + + for(ISelectable thing : selectables) { + Vector4f thingBox = thing.getWorldBox(); + if(rectanglesIntersect( + selectionBoxWorldMin.x, selectionBoxWorldMin.y, + selectionBoxWorldMax.x, selectionBoxWorldMax.y, + thingBox.x, thingBox.y, + thingBox.z, thingBox.w + )) { + selected.add(thing); + } + } + + animation = 0; + broadcastSelectionChanged(); + + } + + private void broadcastSelectionChanged() { + for(ISelectionChangeListener listener : listeners) { + listener.selectionChanged(selected); + } + } + + public void updateSelection(List newSelection) { + selected = newSelection; + broadcastSelectionChanged(); + } + + public boolean rectanglesIntersect( + float minAx, float minAy, float maxAx, float maxAy, + float minBx, float minBy, float maxBx, float maxBy ) { + boolean aLeftOfB = maxAx < minBx; + boolean aRightOfB = minAx > maxBx; + boolean aAboveB = minAy > maxBy; + boolean aBelowB = maxAy < minBy; + + return !( aLeftOfB || aRightOfB || aAboveB || aBelowB ); + } +} diff --git a/src/main/java/xyz/valnet/hadean/gameobjects/SelectionUI.java b/src/main/java/xyz/valnet/hadean/gameobjects/SelectionUI.java new file mode 100644 index 0000000..ffe2a69 --- /dev/null +++ b/src/main/java/xyz/valnet/hadean/gameobjects/SelectionUI.java @@ -0,0 +1,49 @@ +package xyz.valnet.hadean.gameobjects; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import xyz.valnet.engine.scenegraph.GameObject; +import xyz.valnet.hadean.util.Assets; + +public class SelectionUI extends GameObject implements ISelectionChangeListener { + + private List selected = new ArrayList(); + private HashMap selectedTypes = new HashMap(); + + private Selection selectionManager; + + public void start() { + selectionManager = get(Selection.class); + selectionManager.subscribe(this); + } + + public void render() { + if(selected.isEmpty()) return; + + Assets.uiFrame.draw(10, 366, 300, 200); + + int i = 0; + for(String name : selectedTypes.keySet()) { + int n = selectedTypes.get(name); + Assets.font.drawString("" + n + "x " + name, 26, 376 + 16 * i); + i ++; + } + } + + @Override + public void selectionChanged(List selected) { + this.selected = selected; + + selectedTypes.clear(); + for(ISelectable selectable : selected) { + String name = selectable.getClass().getName(); + if(selectedTypes.containsKey(name)) { + selectedTypes.replace(name, selectedTypes.get(name) + 1); + } else { + selectedTypes.put(name, 1); + } + } + } +} diff --git a/src/main/java/xyz/valnet/hadean/scenes/GameScene.java b/src/main/java/xyz/valnet/hadean/scenes/GameScene.java index 75c0e68..ad47bb8 100644 --- a/src/main/java/xyz/valnet/hadean/scenes/GameScene.java +++ b/src/main/java/xyz/valnet/hadean/scenes/GameScene.java @@ -73,8 +73,9 @@ public class GameScene implements IScene { for(int i = 0; i < 3; i ++) { objects.add(new Pawn()); } - Camera camera = new Camera(); - objects.add(camera); + objects.add(new Camera()); + objects.add(new Selection()); + objects.add(new SelectionUI()); for(GameObject obj : objects) { obj.link(this);