/*
* ******************************************************************************
* * Copyright 2015 See AUTHORS file.
* *
* * Licensed under the Apache License, Version 2.0 (the "License");
* * you may not use this file except in compliance with the License.
* * You may obtain a copy of the License at
* *
* * http://www.apache.org/licenses/LICENSE-2.0
* *
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the License for the specific language governing permissions and
* * limitations under the License.
* *****************************************************************************
*/
package com.uwsoft.editor.view.ui.followers;
import com.badlogic.ashley.core.Entity;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.Batch;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.math.*;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.Touchable;
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
import com.uwsoft.editor.renderer.components.PolygonComponent;
import com.uwsoft.editor.renderer.components.TransformComponent;
import com.uwsoft.editor.renderer.utils.PolygonUtils;
import com.uwsoft.editor.renderer.utils.ComponentRetriever;
import com.uwsoft.editor.view.stage.Sandbox;
import java.util.ArrayList;
import java.util.Arrays;
/**
* Created by azakhary on 7/2/2015.
*/
public class PolygonFollower extends SubFollower {
private TransformComponent transformComponent;
private PolygonComponent polygonComponent;
private ArrayList<Vector2> originalPoints;
private Vector2[] drawPoints;
private ShapeRenderer shapeRenderer;
public static final int ANCHOR_SIZE = 9;
public static final int CIRCLE_RADIUS = 10;
private static final Color outlineColor = new Color(200f / 255f, 156f / 255f, 71f / 255f, 1f);
private static final Color innerColor = new Color(200f / 255f, 200f / 255f, 200f / 255f, 0.2f);
private static final Color overColor = new Color(255f / 255f, 94f / 255f, 0f / 255f, 1f);
private static final Color problemColor = new Color(200f / 255f, 0f / 255f, 0f / 255f, 1f);
private int lineIndex = -1;
public int draggingAnchorId = -1;
private int[] intersections = null;
private int selectedAnchorId = -1;
private int pixelsPerWU = 1;
OrthographicCamera runtimeCamera = Sandbox.getInstance().getCamera();
public PolygonFollower(Entity entity) {
super(entity);
pixelsPerWU = Sandbox.getInstance().getPixelPerWU();
setTouchable(Touchable.enabled);
}
public void create() {
polygonComponent = ComponentRetriever.get(entity, PolygonComponent.class);
transformComponent = ComponentRetriever.get(entity, TransformComponent.class);
shapeRenderer = new ShapeRenderer();
}
@Override
protected void setStage(Stage stage) {
super.setStage(stage);
if (stage != null) {
shapeRenderer.setProjectionMatrix(getStage().getCamera().combined);
}
}
public void update() {
if(polygonComponent != null && polygonComponent.vertices != null) {
computeOriginalPoints();
computeDrawPoints();
if(selectedAnchorId == -1) selectedAnchorId = 0;
}
}
public void updateDraw() {
computeDrawPoints();
}
private void computeOriginalPoints() {
originalPoints = new ArrayList<>();
if(polygonComponent == null) return;
originalPoints = new ArrayList<>(Arrays.asList(PolygonUtils.mergeTouchingPolygonsToOne(polygonComponent.vertices)));
}
private void computeDrawPoints() {
drawPoints = originalPoints.toArray(new Vector2[0]);
}
@Override
public void draw(Batch batch, float parentAlpha) {
if(polygonComponent != null && polygonComponent.vertices != null) {
batch.end();
Gdx.gl.glLineWidth(1.7f);
Gdx.gl.glEnable(GL20.GL_BLEND);
Gdx.gl.glBlendFunc(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);
shapeRenderer.setProjectionMatrix(getStage().getCamera().combined);
Matrix4 matrix = batch.getTransformMatrix();
matrix.scale(pixelsPerWU / runtimeCamera.zoom, pixelsPerWU / runtimeCamera.zoom, 1f);
shapeRenderer.setTransformMatrix(matrix);
drawTriangulatedPolygons();
drawOutlines();
drawPoints();
Gdx.gl.glDisable(GL20.GL_BLEND);
batch.begin();
}
}
public void drawOutlines() {
if (drawPoints.length > 0) {
shapeRenderer.begin(ShapeRenderer.ShapeType.Line);
for (int i = 1; i < drawPoints.length; i++) {
shapeRenderer.setColor(outlineColor);
if (lineIndex == i && draggingAnchorId == -1) {
shapeRenderer.setColor(overColor);
}
if(checkIfLineIntersects(i - 1)) {
shapeRenderer.setColor(problemColor);
}
shapeRenderer.line(drawPoints[i].x*transformComponent.scaleX, drawPoints[i].y*transformComponent.scaleY, drawPoints[i - 1].x*transformComponent.scaleX, drawPoints[i - 1].y*transformComponent.scaleY);
}
shapeRenderer.setColor(outlineColor);
if(lineIndex == 0 && draggingAnchorId == -1) {
shapeRenderer.setColor(overColor);
}
if(checkIfLineIntersects(drawPoints.length - 1)) {
shapeRenderer.setColor(problemColor);
}
shapeRenderer.line(drawPoints[drawPoints.length - 1].x*transformComponent.scaleX, drawPoints[drawPoints.length - 1].y*transformComponent.scaleY, drawPoints[0].x*transformComponent.scaleX, drawPoints[0].y*transformComponent.scaleY);
shapeRenderer.end();
}
}
private boolean checkIfLineIntersects(int index) {
if(intersections == null) return false;
for(int i = 0; i < intersections.length; i++) {
if(intersections[i] == index) return true;
}
return false;
}
public void drawTriangulatedPolygons() {
if (polygonComponent.vertices == null) {
return;
}
if(intersections != null) {
return;
}
shapeRenderer.begin(ShapeRenderer.ShapeType.Line);
shapeRenderer.setColor(innerColor);
for (Vector2[] poly : polygonComponent.vertices) {
for (int i = 1; i < poly.length; i++) {
shapeRenderer.line(poly[i - 1].x*transformComponent.scaleX, poly[i - 1].y*transformComponent.scaleY, poly[i].x*transformComponent.scaleX, poly[i].y*transformComponent.scaleY);
}
if (poly.length > 0)
shapeRenderer.line(poly[poly.length - 1].x*transformComponent.scaleX, poly[poly.length - 1].y*transformComponent.scaleY, poly[0].x*transformComponent.scaleX, poly[0].y*transformComponent.scaleY);
}
shapeRenderer.end();
}
public void drawPoints() {
for (int i = 0; i < originalPoints.size(); i++) {
shapeRenderer.begin(ShapeRenderer.ShapeType.Filled);
shapeRenderer.setColor(Color.WHITE);
if(selectedAnchorId == i) {
shapeRenderer.setColor(Color.ORANGE);
}
float side = (float) (ANCHOR_SIZE) / ((float)pixelsPerWU / runtimeCamera.zoom);
float onePixel = 1f/((float)pixelsPerWU / runtimeCamera.zoom);
shapeRenderer.rect(originalPoints.get(i).x*transformComponent.scaleX-side/2f, originalPoints.get(i).y*transformComponent.scaleY-side/2f, side, side);
shapeRenderer.setColor(Color.BLACK);
shapeRenderer.rect(originalPoints.get(i).x*transformComponent.scaleX-side/2f+onePixel, originalPoints.get(i).y*transformComponent.scaleY-side/2f+onePixel, side-2*onePixel, side-2*onePixel);
shapeRenderer.end();
}
}
public void setListener(final PolygonTransformationListener listener) {
clearListeners();
addListener(new ClickListener() {
@Override
public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) {
super.touchDown(event, x, y, pointer, button);
x = x / pixelsPerWU;
y = y / pixelsPerWU;
if(button != Input.Buttons.LEFT) return true;
int anchorId = anchorHitTest(x, y);
if (anchorId >= 0) {
draggingAnchorId = anchorId;
listener.anchorDown(PolygonFollower.this, anchorId, x*runtimeCamera.zoom/transformComponent.scaleX, y*runtimeCamera.zoom/transformComponent.scaleY);
} else if (lineIndex > -1) {
// not anchor but line is selected gotta make new point
listener.vertexDown(PolygonFollower.this, lineIndex, x*runtimeCamera.zoom/transformComponent.scaleX, y*runtimeCamera.zoom/transformComponent.scaleY);
}
return true;
}
@Override
public void touchDragged(InputEvent event, float x, float y, int pointer) {
x = x / pixelsPerWU;
y = y / pixelsPerWU;
int anchorId = draggingAnchorId;
if (anchorId >= 0) {
listener.anchorDragged(PolygonFollower.this, anchorId, x*runtimeCamera.zoom/transformComponent.scaleX, y*runtimeCamera.zoom/transformComponent.scaleY);
} else if (lineIndex > -1) {
}
}
@Override
public void touchUp(InputEvent event, float x, float y, int pointer, int button) {
x = x / pixelsPerWU;
y = y / pixelsPerWU;
if(button != Input.Buttons.LEFT) return;
int anchorId = anchorHitTest(x, y);
lineIndex = vertexHitTest(x, y);
if (anchorId >= 0) {
listener.anchorUp(PolygonFollower.this, anchorId, x*runtimeCamera.zoom/transformComponent.scaleX, y*runtimeCamera.zoom/transformComponent.scaleY);
} else if (lineIndex > -1) {
listener.vertexUp(PolygonFollower.this, lineIndex, x*runtimeCamera.zoom/transformComponent.scaleX, y*runtimeCamera.zoom/transformComponent.scaleY);
}
draggingAnchorId = -1;
}
@Override
public boolean mouseMoved(InputEvent event, float x, float y) {
x = x / pixelsPerWU;
y = y / pixelsPerWU;
int anchorId = anchorHitTest(x, y);
lineIndex = vertexHitTest(x, y);
if(anchorId >= 0) {
lineIndex = -1;
}
if (lineIndex > -1) {
}
return super.mouseMoved(event, x, y);
}
});
}
@Override
public Actor hit (float x, float y, boolean touchable) {
if(originalPoints == null || originalPoints.size() == 0) return null;
x = x / pixelsPerWU;
y = y / pixelsPerWU;
int anchorId = anchorHitTest(x, y);
if(anchorId > -1) {
return this;
}
// checking for vertex intersect
lineIndex = vertexHitTest(x, y);
if(lineIndex > -1) {
return this;
}
return null;
}
private int vertexHitTest(float x, float y) {
Vector2 tmpVector = new Vector2(x, y);
int lineIndex = -1;
float circleSqr = ((float)CIRCLE_RADIUS/pixelsPerWU)*((float)CIRCLE_RADIUS/pixelsPerWU);
for (int i = 1; i < drawPoints.length; i++) {
Vector2 pointOne = drawPoints[i-1].cpy().scl(1f/runtimeCamera.zoom*transformComponent.scaleX, 1f/runtimeCamera.zoom*transformComponent.scaleY);
Vector2 pointTwo = drawPoints[i].cpy().scl(1f/runtimeCamera.zoom*transformComponent.scaleX, 1f/runtimeCamera.zoom*transformComponent.scaleY);
if (Intersector.intersectSegmentCircle(pointOne, pointTwo, tmpVector, circleSqr)) {
lineIndex = i;
break;
}
}
Vector2 pointOne = drawPoints[drawPoints.length - 1].cpy().scl(1f/runtimeCamera.zoom*transformComponent.scaleX, 1f/runtimeCamera.zoom*transformComponent.scaleY);
Vector2 pointTwo = drawPoints[0].cpy().scl(1f/runtimeCamera.zoom*transformComponent.scaleX, 1f/runtimeCamera.zoom*transformComponent.scaleY);
if (drawPoints.length > 0 && Intersector.intersectSegmentCircle(pointOne, pointTwo, tmpVector, circleSqr)) {
lineIndex = 0;
}
if(lineIndex > -1) {
return lineIndex;
}
return -1;
}
private int anchorHitTest(float x, float y) {
if(originalPoints == null || originalPoints.size() == 0) return -1;
for (int i = 0; i < drawPoints.length; i++) {
Circle pointCircle = new Circle(drawPoints[i].x/runtimeCamera.zoom*transformComponent.scaleX, drawPoints[i].y/runtimeCamera.zoom*transformComponent.scaleY, (float)CIRCLE_RADIUS/pixelsPerWU);
if(pointCircle.contains(x, y)) {
return i;
}
}
return -1;
}
public Entity getEntity() {
return entity;
}
public ArrayList<Vector2> getOriginalPoints() {
return originalPoints;
}
public void setSelectedAnchor(int anchorId) {
if(anchorId == -1) return;
selectedAnchorId = anchorId;
}
public int getSelectedAnchorId() {
return selectedAnchorId;
}
public void getSelectedAnchorId(int id) {
if(id < 0) id = 0;
selectedAnchorId = id;
}
public void setProblems(int[] intersections) {
this.intersections = intersections;
}
}