/*
* ******************************************************************************
* * 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.stage.tools;
import com.badlogic.ashley.core.Entity;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.math.Intersector;
import com.badlogic.gdx.math.Vector2;
import com.commons.MsgAPI;
import com.puremvc.patterns.observer.Notification;
import com.uwsoft.editor.Overlap2DFacade;
import com.uwsoft.editor.controller.commands.AddComponentToItemCommand;
import com.uwsoft.editor.controller.commands.RemoveComponentFromItemCommand;
import com.uwsoft.editor.controller.commands.component.UpdatePolygonComponentCommand;
import com.uwsoft.editor.proxy.SceneDataManager;
import com.uwsoft.editor.renderer.components.PolygonComponent;
import com.uwsoft.editor.renderer.utils.ComponentRetriever;
import com.uwsoft.editor.utils.poly.Clipper;
import com.uwsoft.editor.utils.poly.PolygonUtils;
import com.uwsoft.editor.view.stage.Sandbox;
import com.uwsoft.editor.view.ui.FollowersUIMediator;
import com.uwsoft.editor.view.ui.followers.BasicFollower;
import com.uwsoft.editor.view.ui.followers.PolygonFollower;
import com.uwsoft.editor.view.ui.followers.PolygonTransformationListener;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
* Created by azakhary on 7/2/2015.
*/
public class PolygonTool extends SelectionTool implements PolygonTransformationListener {
public static final String NAME = "MESH_TOOL";
private FollowersUIMediator followersUIMediator;
private Vector2 dragLastPoint;
private Object[] currentCommandPayload;
private PolygonFollower lastSelectedMeshFollower = null;
private Vector2[][] polygonBackup = null;
@Override
public void initTool() {
super.initTool();
followersUIMediator = Overlap2DFacade.getInstance().retrieveMediator(FollowersUIMediator.NAME);
updateSubFollowerList();
}
@Override
public void handleNotification(Notification notification) {
switch (notification.getName()) {
case AddComponentToItemCommand.DONE:
updateSubFollowerList();
break;
case RemoveComponentFromItemCommand.DONE:
updateSubFollowerList();
break;
case MsgAPI.ITEM_SELECTION_CHANGED:
updateSubFollowerList();
break;
case MsgAPI.SCENE_LOADED:
updateSubFollowerList();
break;
}
}
@Override
public boolean itemMouseDown(Entity entity, float x, float y) {
lastSelectedMeshFollower = getMeshFollower(entity);
return super.itemMouseDown(entity, x, y);
}
private void setListener(PolygonFollower meshFollower) {
meshFollower.setListener(this);
}
private void updateSubFollowerList() {
Sandbox sandbox = Sandbox.getInstance();
Set<Entity> selectedEntities = sandbox.getSelector().getSelectedItems();
for(Entity entity: selectedEntities) {
BasicFollower follower = followersUIMediator.getFollower(entity);
follower.removeSubFollower(PolygonFollower.class);
PolygonFollower meshFollower = new PolygonFollower(entity);
follower.addSubfollower(meshFollower);
setListener(meshFollower);
lastSelectedMeshFollower = meshFollower;
}
}
@Override
public void vertexUp(PolygonFollower follower, int vertexIndex, float x, float y) {
}
@Override
public void vertexDown(PolygonFollower follower, int vertexIndex, float x, float y) {
PolygonComponent polygonComponent = ComponentRetriever.get(follower.getEntity(), PolygonComponent.class);
currentCommandPayload = UpdatePolygonComponentCommand.payloadInitialState(follower.getEntity());
follower.getOriginalPoints().add(vertexIndex, new Vector2(x, y));
Vector2[] points = follower.getOriginalPoints().toArray(new Vector2[0]);
polygonComponent.vertices = polygonize(points);
follower.updateDraw();
follower.draggingAnchorId = vertexIndex;
dragLastPoint = new Vector2(x, y);
follower.setSelectedAnchor(vertexIndex);
lastSelectedMeshFollower = follower;
polygonBackup = polygonComponent.vertices.clone();
}
@Override
public void VertexMouseOver(PolygonFollower follower, int vertexIndex, float x, float y) {
}
@Override
public void anchorDown(PolygonFollower follower, int anchor, float x, float y) {
dragLastPoint = new Vector2(x, y);
currentCommandPayload = UpdatePolygonComponentCommand.payloadInitialState(follower.getEntity());
follower.setSelectedAnchor(anchor);
lastSelectedMeshFollower = follower;
PolygonComponent polygonComponent = ComponentRetriever.get(follower.getEntity(), PolygonComponent.class);
polygonBackup = polygonComponent.vertices.clone();
}
@Override
public void anchorDragged(PolygonFollower follower, int anchor, float x, float y) {
PolygonComponent polygonComponent = ComponentRetriever.get(follower.getEntity(), PolygonComponent.class);
Vector2[] points = follower.getOriginalPoints().toArray(new Vector2[0]);
Vector2 diff = dragLastPoint.sub(x, y);
points[anchor].sub(diff);
dragLastPoint = new Vector2(x, y);
// check if any of near lines intersect
int[] intersections = checkForIntersection(anchor, points);
if(intersections == null) {
polygonComponent.vertices = polygonize(points);
follower.setProblems(null);
} else {
follower.setProblems(intersections);
}
follower.updateDraw();
}
@Override
public void anchorUp(PolygonFollower follower, int anchor, float x, float y) {
PolygonComponent polygonComponent = ComponentRetriever.get(follower.getEntity(), PolygonComponent.class);
Vector2[] points = follower.getOriginalPoints().toArray(new Vector2[0]);
int[] intersections = checkForIntersection(anchor, points);
if(intersections == null) {
if(PolygonUtils.isPolygonCCW(points)){
Collections.reverse(follower.getOriginalPoints());
points = follower.getOriginalPoints().toArray(new Vector2[0]);
}
polygonComponent.vertices = polygonize(points);
}
if(polygonComponent.vertices == null) {
// restore from backup
polygonComponent.vertices = polygonBackup.clone();
} else if(intersections != null) {
polygonComponent.vertices = polygonBackup.clone();
}
follower.setProblems(null);
currentCommandPayload = UpdatePolygonComponentCommand.payload(currentCommandPayload, polygonComponent.vertices);
Overlap2DFacade.getInstance().sendNotification(MsgAPI.ACTION_UPDATE_MESH_DATA, currentCommandPayload);
}
private Vector2[][] polygonize(Vector2[] vertices) {
return Clipper.polygonize(Clipper.Polygonizer.EWJORDAN, vertices);
}
@Override
public void keyDown(Entity entity, int keycode) {
if(keycode == Input.Keys.DEL || keycode == Input.Keys.FORWARD_DEL) {
if(!deleteSelectedAnchor()) {
super.keyDown(entity, keycode);
}
} else {
super.keyDown(entity, keycode);
}
}
private PolygonFollower getMeshFollower(Entity entity) {
FollowersUIMediator followersUIMediator = Overlap2DFacade.getInstance().retrieveMediator(FollowersUIMediator.NAME);
BasicFollower follower = followersUIMediator.getFollower(entity);
PolygonFollower meshFollower = (PolygonFollower) (follower).getSubFollower(PolygonFollower.class);
return meshFollower;
}
private boolean deleteSelectedAnchor() {
PolygonFollower follower = lastSelectedMeshFollower;
PolygonComponent polygonComponent = ComponentRetriever.get(follower.getEntity(), PolygonComponent.class);
if(follower != null) {
if(polygonComponent == null || polygonComponent.vertices == null || polygonComponent.vertices.length == 0) return false;
if(follower.getOriginalPoints().size() <= 3) return false;
polygonBackup = polygonComponent.vertices.clone();
currentCommandPayload = UpdatePolygonComponentCommand.payloadInitialState(follower.getEntity());
follower.getOriginalPoints().remove(follower.getSelectedAnchorId());
follower.getSelectedAnchorId(follower.getSelectedAnchorId()-1);
Vector2[] points = follower.getOriginalPoints().toArray(new Vector2[0]);
polygonComponent.vertices = polygonize(points);
if(polygonComponent.vertices == null) {
// restore from backup
polygonComponent.vertices = polygonBackup.clone();
follower.update();
}
currentCommandPayload = UpdatePolygonComponentCommand.payload(currentCommandPayload, polygonComponent.vertices);
Overlap2DFacade.getInstance().sendNotification(MsgAPI.ACTION_UPDATE_MESH_DATA, currentCommandPayload);
follower.updateDraw();
return true;
}
return false;
}
private boolean intersectSegments(Vector2[] points, int index1, int index2, int index3, int index4) {
Vector2 intersectionPoint = new Vector2(points[index1]);
boolean isIntersecting = Intersector.intersectSegments(points[index1], points[index2], points[index3], points[index4], intersectionPoint);
if(isIntersecting && !isSamePoint(intersectionPoint, points[index1]) && !isSamePoint(intersectionPoint, points[index2]) && !isSamePoint(intersectionPoint, points[index3]) && !isSamePoint(intersectionPoint, points[index4])) {
return true;
}
return false;
}
private boolean isSamePoint(Vector2 point1, Vector2 point2) {
int pixelsPerWU = Sandbox.getInstance().getPixelPerWU();
int precision = 10000 * pixelsPerWU;
Vector2 pointA = new Vector2(point1);
Vector2 pointB = new Vector2(point2);
pointA.x = Math.round(point1.x * precision) / (float)precision;
pointA.y = Math.round(point1.y * precision) / (float)precision;
pointB.x = Math.round(point2.x * precision) / (float)precision;
pointB.y = Math.round(point2.y * precision) / (float)precision;
return pointA.equals(pointB);
}
private int[] checkForIntersection(int anchor, Vector2[] points) {
int leftPointIndex = points.length-1;
int rightPointIndex = 0;
if(anchor > 0) {
leftPointIndex = anchor-1;
}
if(anchor < points.length-1) {
rightPointIndex = anchor+1;
}
HashSet<Integer> problems = new HashSet<>();
for(int i = 0; i < points.length-1; i++) {
if(i != leftPointIndex && i != anchor) {
if(intersectSegments(points, i, i+1, leftPointIndex, anchor)) {
problems.add(leftPointIndex);
}
if(intersectSegments(points, i, i+1, anchor, rightPointIndex)) {
problems.add(anchor);
}
}
}
if(anchor != points.length-1 && leftPointIndex != points.length-1 && intersectSegments(points, points.length-1, 0, leftPointIndex, anchor)) {
problems.add(leftPointIndex);
}
if(anchor != points.length-1 && leftPointIndex != points.length-1 && intersectSegments(points, points.length-1, 0, anchor, rightPointIndex)) {
problems.add(anchor);
}
if(problems.size() == 0) {
return null;
}
int[] result = problems.stream().mapToInt(i->i).toArray();
return result;
}
}