hadean/src/main/java/xyz/valnet/hadean/gameobjects/inputlayer/SelectionLayer.java

243 lines
6.6 KiB
Java

package xyz.valnet.hadean.gameobjects.inputlayer;
import static xyz.valnet.engine.util.Math.*;
import java.util.ArrayList;
import java.util.List;
import xyz.valnet.engine.App;
import xyz.valnet.engine.graphics.Drawing;
import xyz.valnet.engine.math.Vector2f;
import xyz.valnet.engine.math.Vector2i;
import xyz.valnet.engine.math.Vector4f;
import xyz.valnet.engine.math.Vector4i;
import xyz.valnet.engine.scenegraph.GameObject;
import xyz.valnet.engine.scenegraph.IMouseCaptureArea;
import xyz.valnet.engine.scenegraph.ITransient;
import xyz.valnet.hadean.gameobjects.Camera;
import xyz.valnet.hadean.gameobjects.ui.tabs.BuildTab;
import xyz.valnet.hadean.interfaces.ISelectable;
import xyz.valnet.hadean.interfaces.ISelectionChangeListener;
import xyz.valnet.hadean.util.Assets;
import xyz.valnet.hadean.util.Layers;
public class SelectionLayer extends GameObject implements IMouseCaptureArea, ITransient {
public Vector2f initialCoords;
private Camera camera;
private float animation = 0;
private float animationMax = 30;
private float animationAmplitude = 0.2f;
private List<ISelectionChangeListener> listeners = new ArrayList<ISelectionChangeListener>();
private BuildTab buildTab;
@Override
public void start() {
camera = get(Camera.class);
buildTab = get(BuildTab.class);
}
public void subscribe(ISelectionChangeListener listener) {
listeners.add(listener);
}
// TODO implement click vs single select using distance
// 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));
// }
private List<ISelectable> toRemove = new ArrayList<ISelectable>();
@Override
public void update(float dTime) {
if(animation < animationMax) animation += dTime;
if(animation > animationMax) animation = animationMax;
// if(!active) return;
// if any of our selection just RANDOMLY isnt in the scene anymore, well
// stop selecting it dumbass
for(ISelectable selectable : selected) {
if(selectable instanceof GameObject) {
if(!((GameObject)selectable).inScene()) {
toRemove.add(selectable);
}
}
}
if(!toRemove.isEmpty()) {
for(ISelectable removeMe : toRemove) {
selected.remove(removeMe);
}
toRemove.clear();
broadcastSelectionChanged();
}
}
@Override
public void render() {
Drawing.setLayer(Layers.AREA_SELECT_BOX);
float t = animation / animationMax;
float p = lerp(animationAmplitude, 0, t);
for(ISelectable thing : selected) {
Vector4f box = thing.getWorldBox();
Vector2i min = camera.world2screen(box.x - p, box.y - p);
Vector2i max = camera.world2screen(box.z + p, box.w + p);
Drawing.setLayer(Layers.SELECTION_IDENTIFIERS);
Assets.selectedFrame.draw((int)min.x, (int)min.y, (int)(max.x - min.x), (int)(max.y - min.y));
thing.selectedRender();
}
if(initialCoords != null) {
Vector2i screenPos = camera.world2screen(initialCoords);
Assets.selectionFrame.draw(new Vector4i(
screenPos.x,
screenPos.y,
App.mouseX,
App.mouseY
).toXYWH());
}
}
// 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<ISelectable> selected = new ArrayList<ISelectable>();
private void makeSelection(Vector4f a) {
List<ISelectable> newSelection = new ArrayList<ISelectable>();
Vector4f normalizedSelectionBoxScreen = sortVector(a);
Vector2f selectionBoxWorldMin = new Vector2f(normalizedSelectionBoxScreen.x, normalizedSelectionBoxScreen.y);
Vector2f selectionBoxWorldMax = new Vector2f(normalizedSelectionBoxScreen.z, normalizedSelectionBoxScreen.w);
List<ISelectable> selectables = getAll(ISelectable.class);
int prio = Integer.MIN_VALUE;
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
)) {
int thingPrio = thing.getSelectPriority().toValue();
if(thingPrio > prio) {
newSelection.clear();
prio = thingPrio;
}
if(thingPrio >= prio) {
newSelection.add(thing);
}
}
}
animation = 0;
updateSelection(newSelection);
}
private void broadcastSelectionChanged() {
// Assets.sndSelectionChanged.play();
if(selected.size() > 0) Assets.sndBubble.play();
if(selected.size() == 0) Assets.sndCancel.play();
for(ISelectionChangeListener listener : listeners) {
listener.selectionChanged(selected);
}
}
public void updateSelection(List<ISelectable> newSelection) {
if(selected.size() == 0 && newSelection.size() == 0) return;
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 );
}
private boolean active = false;
@Override
public void mouseEnter() {
active = true;
}
@Override
public void mouseLeave() {
active = false;
}
@Override
public List<Vector4f> getGuiBoxes() {
return List.of(new Vector4f(0, 0, 1000, 1000));
}
@Override
public float getLayer() {
return 0;
}
@Override
public void mouseDown(int button) {
if(!active) return;
if(button == 0) {
if(initialCoords == null) {
initialCoords = camera.screen2world(new Vector2f(App.mouseX, App.mouseY));
}
} else if (button == 1) {
if(selected.size() == 0) {
buildTab.rightClickOnWorld();
} else {
clearSelection();
}
}
}
public void clearSelection() {
if(selected.size() == 0) return;
selected.clear();
broadcastSelectionChanged();
}
@Override
public void mouseUp(int button) {
if(initialCoords != null && button == 0) {
Vector2f worldMouse = camera.screen2world(App.mouseX, App.mouseY);
Vector4f worldBox = new Vector4f(
initialCoords.x,
initialCoords.y,
worldMouse.x,
worldMouse.y
);
makeSelection(worldBox);
initialCoords = null;
}
}
}