/**
* ORIPA - Origami Pattern Editor
* Copyright (C) 2005-2009 Jun Mitani http://mitani.cs.tsukuba.ac.jp/
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package oripa;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Stack;
import javax.swing.JOptionPane;
import javax.vecmath.Vector2d;
import oripa.geom.*;
class PointComparatorX implements Comparator {
@Override
public int compare(Object v1, Object v2) {
if(((Vector2d) v1).x > ((Vector2d) v2).x)
return 1;
else if(((Vector2d) v1).x < ((Vector2d) v2).x)
return -1;
else
return 0;
}
}
class PointComparatorY implements Comparator {
@Override
public int compare(Object v1, Object v2) {
if(((Vector2d) v1).y > ((Vector2d) v2).y)
return 1;
else if(((Vector2d) v1).y < ((Vector2d) v2).y)
return -1;
else
return 0;
}
}
class FaceOrderComparator implements Comparator {
@Override
public int compare(Object f1, Object f2) {
if(((OriFace) f1).z_order > ((OriFace) f2).z_order)
return 1;
else if (((OriFace) f1).z_order < ((OriFace) f2).z_order)
return -1;
else
return 0;
}
}
class UndoInfo {
public ArrayList<OriLine> lines = new ArrayList<OriLine>();
}
public class Doc {
public ArrayList<OriLine> crossLines = new ArrayList<>();
public ArrayList<OriLine> lines = new ArrayList<>();
public ArrayList<OriFace> faces = new ArrayList<>();
public ArrayList<OriVertex> vertices = new ArrayList<>();
public ArrayList<OriEdge> edges = new ArrayList<>();
public ArrayList<OriLine> tmpSelectedLines = new ArrayList<>();
private Stack<UndoInfo> undoStack = new Stack<UndoInfo>();
public boolean isValidPattern = false;
public double size;
public boolean hasModel = false;
public boolean bFolded = false;
public ArrayList<OriFace> sortedFaces = new ArrayList<>();
public static final double POINT_EPS = 1.0;
boolean bOutLog = true;
public String dataFilePath = "";
final public static int NO_OVERLAP = 0;
final public static int UPPER = 1;
final public static int LOWER = 2;
final public static int UNDEFINED = 9;
public int overlapRelation[][];
public ArrayList<int[][]> overlapRelations = new ArrayList<>();
public int currentORmatIndex;
public String title;
public String editorName;
public String originalAuthorName;
public String reference;
public String memo;
public Vector2d foldedBBoxLT;
public Vector2d foldedBBoxRB;
int debugCount = 0;
public Doc(double size) {
this.size = size;
OriLine l0 = new OriLine(-size / 2.0, -size / 2.0, size / 2.0, -size / 2.0, OriLine.TYPE_CUT);
OriLine l1 = new OriLine(size / 2.0, -size / 2.0, size / 2.0, size / 2.0, OriLine.TYPE_CUT);
OriLine l2 = new OriLine(size / 2.0, size / 2.0, -size / 2.0, size / 2.0, OriLine.TYPE_CUT);
OriLine l3 = new OriLine(-size / 2.0, size / 2.0, -size / 2.0, -size / 2.0, OriLine.TYPE_CUT);
lines.add(l0);
lines.add(l1);
lines.add(l2);
lines.add(l3);
}
public void setNextORMat() {
if (currentORmatIndex < overlapRelations.size() - 1) {
currentORmatIndex++;
Folder.matrixCopy(overlapRelations.get(currentORmatIndex), overlapRelation);
}
}
public void setPrevORMat() {
if (currentORmatIndex > 0) {
currentORmatIndex--;
Folder.matrixCopy(overlapRelations.get(currentORmatIndex), overlapRelation);
}
}
public int getSelectedLineNum() {
int count = 0;
for (OriLine l : lines) {
if (l.selected) {
count++;
}
}
return count;
}
public void pushUndoInfo() {
UndoInfo ui = new UndoInfo();
for (OriLine l : lines) {
ui.lines.add(new OriLine(l));
}
undoStack.push(ui);
}
public void popUndoInfo() {
if (undoStack.isEmpty()) {
return;
}
UndoInfo ui = undoStack.pop();
lines.clear();
lines.addAll(ui.lines);
}
private OriVertex addAndGetVertexFromVVec(Vector2d p) {
OriVertex vtx = null;
for (OriVertex v : vertices) {
if (GeomUtil.Distance(v.p, p) < POINT_EPS) {
vtx = v;
}
}
if (vtx == null) {
vtx = new OriVertex(p);
vertices.add(vtx);
}
return vtx;
}
public void prepareForCopyAndPaste() {
tmpSelectedLines.clear();
for (OriLine l : lines) {
if (l.selected) {
tmpSelectedLines.add(l);
}
}
}
// Turn the model over
public void filpAll() {
Vector2d maxV = new Vector2d(-Double.MAX_VALUE, -Double.MAX_VALUE);
Vector2d minV = new Vector2d(Double.MAX_VALUE, Double.MAX_VALUE);
for (OriFace face : ORIPA.doc.faces) {
face.z_order = -face.z_order;
for (OriHalfedge he : face.halfedges) {
maxV.x = Math.max(maxV.x, he.vertex.p.x);
maxV.y = Math.max(maxV.y, he.vertex.p.y);
minV.x = Math.min(minV.x, he.vertex.p.x);
minV.y = Math.min(minV.y, he.vertex.p.y);
}
}
double centerX = (maxV.x + minV.x) / 2;
for (OriFace face : ORIPA.doc.faces) {
for (OriHalfedge he : face.halfedges) {
he.positionForDisplay.x = 2 * centerX - he.positionForDisplay.x;
}
}
for (OriFace face : ORIPA.doc.faces) {
face.faceFront = !face.faceFront;
face.setOutline();
}
Collections.sort(faces, new FaceOrderComparator());
Collections.reverse(sortedFaces);
}
//
public void setFacesOutline(boolean isSlide) {
int minDepth = Integer.MAX_VALUE;
int maxDepth = -Integer.MAX_VALUE;
for (OriFace f : faces) {
minDepth = Math.min(minDepth, f.z_order);
maxDepth = Math.max(minDepth, f.z_order);
for (OriHalfedge he : f.halfedges) {
he.positionForDisplay.set(he.vertex.p);
}
f.setOutline();
}
if (isSlide) {
double slideUnit = 10.0 / (maxDepth - minDepth);
for (OriVertex v : vertices) {
v.tmpFlg = false;
v.tmpVec.set(v.p);
}
for (OriFace f : faces) {
Vector2d faceCenter = new Vector2d();
for (OriHalfedge he : f.halfedges) {
faceCenter.add(he.vertex.p);
}
faceCenter.scale(1.0 / f.halfedges.size());
for (OriHalfedge he : f.halfedges) {
if (he.vertex.tmpFlg) {
continue;
}
he.vertex.tmpFlg = true;
he.vertex.tmpVec.x += slideUnit * f.z_order;
he.vertex.tmpVec.y += slideUnit * f.z_order;
Vector2d dirToCenter = new Vector2d(faceCenter);
dirToCenter.sub(he.vertex.tmpVec);
dirToCenter.normalize();
dirToCenter.scale(6.0);
he.vertex.tmpVec.add(dirToCenter);
}
}
for (OriFace f : faces) {
for (OriHalfedge he : f.halfedges) {
he.positionForDisplay.set(he.vertex.tmpVec);
}
f.setOutline();
}
}
}
public boolean cleanDuplicatedLines() {
debugCount = 0;
System.out.println("pre cleanDuplicatedLines " + lines.size());
ArrayList<OriLine> tmpLines = new ArrayList<>();
for (OriLine l : lines) {
OriLine ll = l;
boolean bSame = false;
// Test if the line is already in tmpLines to prevent duplicity
for (OriLine line : tmpLines) {
if (GeomUtil.isSameLineSegment(line, ll)) {
bSame = true;
break;
}
}
if (bSame) {
continue;
}
tmpLines.add(ll);
}
if (lines.size() == tmpLines.size()) {
return false;
}
lines.clear();
lines.addAll(tmpLines);
System.out.println("after cleanDuplicatedLines " + lines.size());
return true;
}
// Unselect all lines
public void resetSelectedOriLines() {
for (OriLine line : lines) {
line.selected = false;
}
}
public void selectAllOriLines() {
for (OriLine l : lines) {
if (l.type != OriLine.TYPE_CUT) {
l.selected = true;
}
}
}
private OriLine getMirrorCopiedLine(OriLine line, OriLine baseOriLine) {
Line baseLine = baseOriLine.getLine();
double dist0 = GeomUtil.Distance(line.p0, baseLine);
Vector2d dir0 = new Vector2d();
if (GeomUtil.isRightSide(line.p0, baseLine)) {
dir0.set(-baseLine.dir.y, baseLine.dir.x);
} else {
dir0.set(baseLine.dir.y, -baseLine.dir.x);
}
dir0.normalize();
Vector2d q0 = new Vector2d(
line.p0.x + dir0.x * dist0 * 2,
line.p0.y + dir0.y * dist0 * 2);
double dist1 = GeomUtil.Distance(line.p1, baseLine);
Vector2d dir1 = new Vector2d();
if (GeomUtil.isRightSide(line.p1, baseLine)) {
dir1.set(-baseLine.dir.y, baseLine.dir.x);
} else {
dir1.set(baseLine.dir.y, -baseLine.dir.x);
}
dir1.normalize();
Vector2d q1 = new Vector2d(
line.p1.x + dir1.x * dist1 * 2,
line.p1.y + dir1.y * dist1 * 2);
OriLine oriLine = new OriLine(q0, q1, line.type);
return oriLine;
}
public void mirrorCopyBy(OriLine l) {
ArrayList<OriLine> copiedLines = new ArrayList<>();
for (OriLine line : lines) {
if (!line.selected) {
continue;
}
if (line == l) {
continue;
}
copiedLines.add(getMirrorCopiedLine(line, l));
}
for (OriLine line : copiedLines) {
addLine(line);
}
}
public void removeLine(OriLine l) {
lines.remove(l);
// merge the lines if possible, to prevent unnecessary vertexes
merge2LinesAt(l.p0);
merge2LinesAt(l.p1);
}
public void removeVertex(Vector2d v) {
merge2LinesAt(v);
}
public void deleteSelectedLines() {
ArrayList<OriLine> selectedLines = new ArrayList<>();
for (OriLine line : lines) {
if (line.selected) {
selectedLines.add(line);
}
}
for (OriLine line : selectedLines) {
lines.remove(line);
}
}
private void merge2LinesAt(Vector2d p) {
ArrayList<OriLine> sharedLines = new ArrayList<>();
for (OriLine line : lines) {
if (GeomUtil.Distance(line.p0, p) < 0.001 || GeomUtil.Distance(line.p1, p) < 0.001) {
sharedLines.add(line);
}
}
if (sharedLines.size() != 2) {
return;
}
OriLine l0 = sharedLines.get(0);
OriLine l1 = sharedLines.get(1);
if (l0.type != l1.type) {
return;
}
// Check if the lines have the same angle
Vector2d dir0 = new Vector2d(l0.p1.x - l0.p0.x, l0.p1.y - l0.p0.y);
Vector2d dir1 = new Vector2d(l1.p1.x - l1.p0.x, l1.p1.y - l1.p0.y);
dir0.normalize();
dir1.normalize();
if (!GeomUtil.isParallel(dir0, dir1)) {
return;
}
// Merge possibility found
Vector2d p0 = new Vector2d();
Vector2d p1 = new Vector2d();
if (GeomUtil.Distance(l0.p0, p) < 0.001) {
p0.set(l0.p1);
} else {
p0.set(l0.p0);
}
if (GeomUtil.Distance(l1.p0, p) < 0.001) {
p1.set(l1.p1);
} else {
p1.set(l1.p0);
}
lines.remove(l0);
lines.remove(l1);
OriLine li = new OriLine(p0, p1, l0.type);
lines.add(li);
}
public void CircleCopy(double cx, double cy, double angleDeg, int num) {
ArrayList<OriLine> copiedLines = new ArrayList<>();
pushUndoInfo();
oripa.geom.RectangleClipper clipper = new oripa.geom.RectangleClipper(-size / 2, -size / 2, size / 2, size / 2);
double angle = angleDeg * Math.PI / 180.0;
for (int i = 0; i < num; i++) {
double angleRad = angle * (i + 1);
for (OriLine l : lines) {
if (!l.selected) {
continue;
}
OriLine cl = new OriLine(l);
double tx0 = l.p0.x - cx;
double ty0 = l.p0.y - cy;
double tx1 = l.p1.x - cx;
double ty1 = l.p1.y - cy;
double ttx0 = tx0 * Math.cos(angleRad) - ty0 * Math.sin(angleRad);
double tty0 = tx0 * Math.sin(angleRad) + ty0 * Math.cos(angleRad);
double ttx1 = tx1 * Math.cos(angleRad) - ty1 * Math.sin(angleRad);
double tty1 = tx1 * Math.sin(angleRad) + ty1 * Math.cos(angleRad);
cl.p0.x = ttx0 + cx;
cl.p0.y = tty0 + cy;
cl.p1.x = ttx1 + cx;
cl.p1.y = tty1 + cy;
if (clipper.clip(cl)) {
copiedLines.add(cl);
}
}
}
for (OriLine l : copiedLines) {
addLine(l);
}
resetSelectedOriLines();
}
public void ArrayCopy(int row, int col, double interX, double interY, boolean bFillSheet) {
int startRow = bFillSheet ? (int) (-size / interY) : 0;
int startCol = bFillSheet ? (int) (-size / interX) : 0;
int endRow = bFillSheet ? (int) (size / interY + 0.5) : row;
int endCol = bFillSheet ? (int) (size / interX + 0.5) : col;
pushUndoInfo();
System.out.println("startRow=" + startRow + " startCol=" + startCol + " endRow=" + endRow + " endCol=" + endCol);
int lineNum = lines.size();
ArrayList<OriLine> copiedLines = new ArrayList<>();
oripa.geom.RectangleClipper clipper = new oripa.geom.RectangleClipper(-size / 2, -size / 2, size / 2, size / 2);
for (int x = startCol; x < endCol; x++) {
for (int y = startRow; y < endRow; y++) {
if (x == 0 && y == 0) {
continue;
}
// copies the selected lines
for (int i = 0; i < lineNum; i++) {
OriLine l = lines.get(i);
if (!l.selected) {
continue;
}
OriLine cl = new OriLine(l);
cl.p0.x += interX * x;
cl.p0.y += interY * y;
cl.p1.x += interX * x;
cl.p1.y += interY * y;
if (clipper.clip(cl)) {
copiedLines.add(cl);
}
}
}
}
for (OriLine l : copiedLines) {
addLine(l);
}
resetSelectedOriLines();
}
public boolean buildOrigami(boolean needCleanUp) {
edges.clear();
vertices.clear();
faces.clear();
int lineNum = lines.size();
for (int i = 0; i < lineNum; i++) {
OriLine l = lines.get(i);
if (l.type == OriLine.TYPE_NONE) {
continue;
}
OriVertex sv = addAndGetVertexFromVVec(l.p0);
OriVertex ev = addAndGetVertexFromVVec(l.p1);
OriEdge eg = new OriEdge(sv, ev, l.type);
edges.add(eg);
sv.addEdge(eg);
ev.addEdge(eg);
}
for (OriVertex v : vertices) {
for (OriEdge e : v.edges) {
if (e.type == OriLine.TYPE_CUT) {
continue;
}
if (v == e.sv) {
if (e.left != null) {
continue;
}
} else {
if (e.right != null) {
continue;
}
}
OriFace face = new OriFace();
faces.add(face);
OriVertex walkV = v;
OriEdge walkE = e;
debugCount = 0;
while (true) {
if (debugCount++ > 200) {
System.out.println("ERROR");
return false;
}
OriHalfedge he = new OriHalfedge(walkV, face);
face.halfedges.add(he);
he.tmpInt = walkE.type;
if (walkE.sv == walkV) {
walkE.left = he;
} else {
walkE.right = he;
}
walkV = walkE.oppositeVertex(walkV);
walkE = walkV.getPrevEdge(walkE);
if (walkV == v) {
break;
}
}
face.makeHalfedgeLoop();
face.setOutline();
face.setPreOutline();
}
}
this.makeEdges();
for (OriEdge e : edges) {
e.type = e.left.tmpInt;
}
hasModel = true;
return true;
}
public boolean buildOrigami3(boolean needCleanUp) {
edges.clear();
vertices.clear();
faces.clear();
// Remove lines with the same position
debugCount = 0;
if (needCleanUp) {
if (cleanDuplicatedLines()) {
JOptionPane.showMessageDialog(
ORIPA.mainFrame, "Removing multiples edges with the same position ",
"Simplifying CP", JOptionPane.INFORMATION_MESSAGE);
}
}
int lineNum = lines.size();
// Create the edges from the vertexes
for (int i = 0; i < lineNum; i++) {
OriLine l = lines.get(i);
if (l.type == OriLine.TYPE_NONE) {
continue;
}
OriVertex sv = addAndGetVertexFromVVec(l.p0);
OriVertex ev = addAndGetVertexFromVVec(l.p1);
OriEdge eg = new OriEdge(sv, ev, l.type);
edges.add(eg);
sv.addEdge(eg);
ev.addEdge(eg);
}
// Check if there are vertexes with just 2 collinear edges with same type
// merge the edges and delete the vertex for efficiency
ArrayList<OriEdge> eds = new ArrayList<>();
ArrayList<OriVertex> tmpVVec = new ArrayList<>();
tmpVVec.addAll(vertices);
for (OriVertex v : tmpVVec) {
eds.clear();
for (OriEdge e : edges) {
if (e.sv == v || e.ev == v) {
eds.add(e);
}
}
if (eds.size() != 2) {
continue;
}
// If the types of the edges are different, do nothing
if (eds.get(0).type != eds.get(1).type) {
continue;
}
OriEdge e0 = eds.get(0);
OriEdge e1 = eds.get(1);
// Check if they are collinear
Vector2d dir0 = new Vector2d(e0.ev.p.x - e0.sv.p.x, e0.ev.p.y - e0.sv.p.y);
Vector2d dir1 = new Vector2d(e1.ev.p.x - e1.sv.p.x, e1.ev.p.y - e1.sv.p.y);
dir0.normalize();
dir1.normalize();
if (GeomUtil.Distance(dir0, dir1) > 0.001
&& Math.abs(GeomUtil.Distance(dir0, dir1) - 2.0) > 0.001) {
continue;
}
// found mergeable edge
edges.remove(e0);
edges.remove(e1);
vertices.remove(v);
e0.sv.edges.remove(e0);
e0.ev.edges.remove(e0);
e1.sv.edges.remove(e1);
e1.ev.edges.remove(e1);
if (e0.sv == v && e1.sv == v) {
OriEdge ne = new OriEdge(e0.ev, e1.ev, e0.type);
edges.add(ne);
ne.sv.addEdge(ne);
ne.ev.addEdge(ne);
} else if (e0.sv == v && e1.ev == v) {
OriEdge ne = new OriEdge(e0.ev, e1.sv, e0.type);
edges.add(ne);
ne.sv.addEdge(ne);
ne.ev.addEdge(ne);
} else if (e0.ev == v && e1.sv == v) {
OriEdge ne = new OriEdge(e0.sv, e1.ev, e0.type);
edges.add(ne);
ne.sv.addEdge(ne);
ne.ev.addEdge(ne);
} else {
OriEdge ne = new OriEdge(e0.sv, e1.sv, e0.type);
edges.add(ne);
ne.sv.addEdge(ne);
ne.ev.addEdge(ne);
}
}
// System.out.println("vnum=" + vertices.size());
// System.out.println("enum=" + edges.size());
// Construct the faces
for (OriVertex v : vertices) {
for (OriEdge e : v.edges) {
if (e.type == OriLine.TYPE_CUT) {
continue;
}
if (v == e.sv) {
if (e.left != null) {
continue;
}
} else {
if (e.right != null) {
continue;
}
}
OriFace face = new OriFace();
faces.add(face);
OriVertex walkV = v;
OriEdge walkE = e;
debugCount = 0;
while (true) {
if (debugCount++ > 100) {
System.out.println("ERROR");
return false;
}
OriHalfedge he = new OriHalfedge(walkV, face);
face.halfedges.add(he);
he.tmpInt = walkE.type;
if (walkE.sv == walkV) {
walkE.left = he;
} else {
walkE.right = he;
}
walkV = walkE.oppositeVertex(walkV);
walkE = walkV.getPrevEdge(walkE);
if (walkV == v) {
break;
}
}
face.makeHalfedgeLoop();
face.setOutline();
face.setPreOutline();
}
}
this.makeEdges();
for (OriEdge e : edges) {
e.type = e.left.tmpInt;
}
hasModel = true;
return checkPatternValidity();
}
public boolean checkPatternValidity() {
boolean isOK = true;
// Check if the faces are convex
for (OriFace face : faces) {
if (face.halfedges.size() == 3) {
continue;
}
OriHalfedge baseHe = face.halfedges.get(0);
boolean baseFlg = GeomUtil.CCWcheck(baseHe.prev.vertex.p,
baseHe.vertex.p, baseHe.next.vertex.p);
for (int i = 1; i < face.halfedges.size(); i++) {
OriHalfedge he = face.halfedges.get(i);
if (GeomUtil.CCWcheck(he.prev.vertex.p, he.vertex.p, he.next.vertex.p) != baseFlg) {
isOK = false;
face.hasProblem = true;
break;
}
}
}
// Check Maekawa's theorem for all vertexes
for (OriVertex v : vertices) {
int ridgeCount = 0;
int valleyCount = 0;
boolean isCorner = false;
for (OriEdge e : v.edges) {
if (e.type == OriLine.TYPE_RIDGE) {
ridgeCount++;
} else if (e.type == OriLine.TYPE_VALLEY) {
valleyCount++;
} else if (e.type == OriLine.TYPE_CUT) {
isCorner = true;
break;
}
}
if (isCorner) {
continue;
}
if (Math.abs(ridgeCount - valleyCount) != 2) {
System.out.println("edge type count invalid: "+ v+" "+Math.abs(ridgeCount - valleyCount));
v.hasProblem = true;
isOK = false;
}
}
// Check Kawasaki's theorem for every vertex
for (OriVertex v : vertices) {
if (v.hasProblem) {
continue;
}
Vector2d p = v.p;
double oddSum = 0;
double evenSum = 0;
boolean isCorner = false;
for (int i = 0; i < v.edges.size(); i++) {
OriEdge e = v.edges.get(i);
if (e.type == OriLine.TYPE_CUT) {
isCorner = true;
break;
}
Vector2d preP = new Vector2d(v.edges.get(i).oppositeVertex(v).p);
Vector2d nxtP = new Vector2d(v.edges.get((i + 1) % v.edges.size()).oppositeVertex(v).p);
nxtP.sub(p);
preP.sub(p);
if (i % 2 == 0) {
oddSum += preP.angle(nxtP);
} else {
evenSum += preP.angle(nxtP);
}
}
if (isCorner) {
continue;
}
//System.out.println("oddSum = " + oddSum + "/ evenSum = " + evenSum);
if (Math.abs(oddSum - Math.PI) > Math.PI / 180 / 2) {
System.out.println("edge angle sum invalid");
v.hasProblem = true;
isOK = false;
}
}
isValidPattern = isOK;
calcFoldedBoundingBox();
return isOK;
}
boolean sortFinished = false;
public boolean isLineCrossFace4(OriFace face, OriHalfedge heg) {
Vector2d p1 = heg.positionAfterFolded;
Vector2d p2 = heg.next.positionAfterFolded;
Vector2d dir = new Vector2d();
dir.sub(p2, p1);
Line heLine = new Line(p1, dir);
for (OriHalfedge he : face.halfedges) {
// About the relation of contours (?)
// Check if the line is on the countour of the face
if (GeomUtil.DistancePointToLine(he.positionAfterFolded, heLine) < 1
&& GeomUtil.DistancePointToLine(he.next.positionAfterFolded, heLine) < 1) {
return false;
}
}
Vector2d preCrossPoint = null;
for (OriHalfedge he : face.halfedges) {
// Checks if the line crosses any of the edges of the face
Vector2d cp = GeomUtil.getCrossPoint(he.positionAfterFolded, he.next.positionAfterFolded, heg.positionAfterFolded, heg.next.positionAfterFolded);
if (cp == null) {
continue;
}
if (preCrossPoint == null) {
preCrossPoint = cp;
} else {
if (GeomUtil.Distance(cp, preCrossPoint) > size * 0.001) {
return true;
}
}
}
// Checkes if the line is in the interior of the face
if (isOnFace(face, heg.positionAfterFolded)) {
return true;
}
if (isOnFace(face, heg.next.positionAfterFolded)) {
return true;
}
return false;
}
public boolean isOnFace(OriFace face, Vector2d v) {
int heNum = face.halfedges.size();
// Return false if the vector is on the contour of the face
for (int i = 0; i < heNum; i++) {
OriHalfedge he = face.halfedges.get(i);
if (GeomUtil.DistancePointToSegment(v, he.positionAfterFolded, he.next.positionAfterFolded) < size * 0.001) {
return false;
}
}
OriHalfedge baseHe = face.halfedges.get(0);
boolean baseFlg = GeomUtil.CCWcheck(baseHe.positionAfterFolded, baseHe.next.positionAfterFolded, v);
for (int i = 1; i < heNum; i++) {
OriHalfedge he = face.halfedges.get(i);
if (GeomUtil.CCWcheck(he.positionAfterFolded, he.next.positionAfterFolded, v) != baseFlg) {
return false;
}
}
return true;
}
public boolean addVertexOnLine(OriLine line, Vector2d v) {
// Normally you dont want to add a vertex too close to the end of the line
if (GeomUtil.Distance(line.p0, v) < this.size * 0.001
|| GeomUtil.Distance(line.p1, v) < this.size * 0.001) {
return false;
}
OriLine l0 = new OriLine(line.p0, v, line.type);
OriLine l1 = new OriLine(v, line.p1, line.type);
lines.remove(line);
lines.add(l0);
lines.add(l1);
return true;
}
public boolean foldWithoutLineType() {
for (OriFace face : faces) {
face.faceFront = true;
}
faces.get(0).z_order = 0;
debugCount = 0;
walkFace(faces.get(0));
Collections.sort(faces, new FaceOrderComparator());
sortedFaces.clear();
sortedFaces.addAll(faces);
for (OriEdge e : edges) {
e.sv.p.set(e.left.tmpVec);
e.sv.tmpFlg = false;
}
setFacesOutline(false);
calcFoldedBoundingBox();
return true;
}
public void calcFoldedBoundingBox() {
foldedBBoxLT = new Vector2d(Double.MAX_VALUE, Double.MAX_VALUE);
foldedBBoxRB = new Vector2d(-Double.MAX_VALUE, -Double.MAX_VALUE);
for (OriFace face : faces) {
for (OriHalfedge he : face.halfedges) {
foldedBBoxLT.x = Math.min(foldedBBoxLT.x, he.tmpVec.x);
foldedBBoxLT.y = Math.min(foldedBBoxLT.y, he.tmpVec.y);
foldedBBoxRB.x = Math.max(foldedBBoxRB.x, he.tmpVec.x);
foldedBBoxRB.y = Math.max(foldedBBoxRB.y, he.tmpVec.y);
}
}
}
// Make the folds by flipping the faces
private void walkFace(OriFace face) {
face.tmpFlg = true;
if (debugCount++ > 1000) {
System.out.println("walkFace too deap");
return;
}
for (OriHalfedge he : face.halfedges) {
if (he.pair == null) {
continue;
}
if (he.pair.face.tmpFlg) {
continue;
}
flipFace2(he.pair.face, he);
he.pair.face.tmpFlg = true;
walkFace(he.pair.face);
}
}
// Method that doesnt use sin con
private void flipFace2(OriFace face, OriHalfedge baseHe) {
Vector2d preOrigin = new Vector2d(baseHe.pair.next.tmpVec);
Vector2d afterOrigin = new Vector2d(baseHe.tmpVec);
// Creates the base unit vector for before the rotation
Vector2d baseDir = new Vector2d();
baseDir.sub(baseHe.pair.tmpVec, baseHe.pair.next.tmpVec);
// Creates the base unit vector for after the rotation
Vector2d afterDir = new Vector2d();
afterDir.sub(baseHe.next.tmpVec, baseHe.tmpVec);
afterDir.normalize();
Line preLine = new Line(preOrigin, baseDir);
for (OriHalfedge he : face.halfedges) {
double param[] = new double[1];
double d0 = GeomUtil.Distance(he.tmpVec, preLine, param);
double d1 = param[0];
Vector2d footV = new Vector2d(afterOrigin);
footV.x += d1 * afterDir.x;
footV.y += d1 * afterDir.y;
Vector2d afterDirFromFoot = new Vector2d();
afterDirFromFoot.x = afterDir.y;
afterDirFromFoot.y = -afterDir.x;
he.tmpVec.x = footV.x + d0 * afterDirFromFoot.x;
he.tmpVec.y = footV.y + d0 * afterDirFromFoot.y;
}
// Ivertion
if (face.faceFront == baseHe.face.faceFront) {
Vector2d ep = baseHe.next.tmpVec;
Vector2d sp = baseHe.tmpVec;
Vector2d b = new Vector2d();
b.sub(ep, sp);
for (OriHalfedge he : face.halfedges) {
if (GeomUtil.Distance(he.tmpVec, new Line(sp, b)) < GeomUtil.EPS) {
continue;
}
if (Math.abs(b.y) < GeomUtil.EPS) {
Vector2d a = new Vector2d();
a.sub(he.tmpVec, sp);
a.y = -a.y;
he.tmpVec.y = a.y + sp.y;
} else {
Vector2d a = new Vector2d();
a.sub(he.tmpVec, sp);
he.tmpVec.y = ((b.y * b.y - b.x * b.x) * a.y + 2 * b.x * b.y * a.x) / b.lengthSquared();
he.tmpVec.x = b.x / b.y * a.y - a.x + b.x / b.y * he.tmpVec.y;
he.tmpVec.x += sp.x;
he.tmpVec.y += sp.y;
}
}
face.faceFront = !face.faceFront;
}
faces.remove(face);
faces.add(face);
}
// Adds a rabbit-ear molecule given a triangle
public void addTriangleDivideLines(Vector2d v0, Vector2d v1, Vector2d v2) {
Vector2d c = GeomUtil.getIncenter(v0, v1, v2);
if (c == null) {
System.out.print("Failed to calculate incenter of the triangle");
}
addLine(new OriLine(c, v0, Globals.inputLineType));
addLine(new OriLine(c, v1, Globals.inputLineType));
addLine(new OriLine(c, v2, Globals.inputLineType));
}
// Adds perpendicular bisector
public void addPBisector(Vector2d v0, Vector2d v1) {
Vector2d cp = new Vector2d(v0);
cp.add(v1);
cp.scale(0.5);
Vector2d dir = new Vector2d();
dir.sub(v0, v1);
double tmp = dir.y;
dir.y = -dir.x;
dir.x = tmp;
dir.scale(Constants.DEFAULT_PAPER_SIZE * 8);
OriLine l = new OriLine(cp.x - dir.x, cp.y - dir.y, cp.x + dir.x, cp.y + dir.y, Globals.inputLineType);
GeomUtil.clipLine(l, size / 2);
addLine(l);
}
// v1-v2 is the symmetry line, v0-v1 is the sbject to be copied.
public void addSymmetricLine(Vector2d v0, Vector2d v1, Vector2d v2) {
Vector2d v3 = GeomUtil.getSymmetricPoint(v0, v1, v2);
Ray ray = new Ray(v1, new Vector2d(v3.x - v1.x, v3.y - v1.y));
double minDist = Double.MAX_VALUE;
Vector2d bestPoint = null;
for (OriLine l : lines) {
Vector2d crossPoint = GeomUtil.getCrossPoint(ray, l.getSegment());
if (crossPoint == null) {
continue;
}
double distance = GeomUtil.Distance(crossPoint, v1);
if (distance < POINT_EPS) {
continue;
}
if (distance < minDist) {
minDist = distance;
bestPoint = crossPoint;
}
}
if (bestPoint == null) {
return;
}
addLine(new OriLine(v1, bestPoint, Globals.inputLineType));
}
// v1-v2 is the symmetry line, v0-v1 is the sbject to be copied.
// automatically generates possible rebouncing of the fold (used when Ctrl is pressed)
public void addSymmetricLineAutoWalk(Vector2d v0, Vector2d v1, Vector2d v2, int stepCount, Vector2d startV) {
stepCount++;
if (stepCount > 36) {
return;
}
Vector2d v3 = GeomUtil.getSymmetricPoint(v0, v1, v2);
Ray ray = new Ray(v1, new Vector2d(v3.x - v1.x, v3.y - v1.y));
double minDist = Double.MAX_VALUE;
Vector2d bestPoint = null;
OriLine bestLine = null;
for (OriLine l : lines) {
Vector2d crossPoint = GeomUtil.getCrossPoint(ray, l.getSegment());
if (crossPoint == null) {
continue;
}
double distance = GeomUtil.Distance(crossPoint, v1);
if (distance < POINT_EPS) {
continue;
}
if (distance < minDist) {
minDist = distance;
bestPoint = crossPoint;
bestLine = l;
}
}
if (bestPoint == null) {
return;
}
addLine(new OriLine(v1, bestPoint, Globals.inputLineType));
if (GeomUtil.Distance(bestPoint, startV) < POINT_EPS) {
return;
}
addSymmetricLineAutoWalk(v1, bestPoint, bestLine.p0, stepCount, startV);
}
public void addBisectorLine(Vector2d v0, Vector2d v1, Vector2d v2, OriLine l) {
Vector2d dir = GeomUtil.getBisectorVec(v0, v1, v2);
Vector2d cp = GeomUtil.getCrossPoint(new Line(l.p0, new Vector2d(l.p1.x - l.p0.x, l.p1.y - l.p0.y)), new Line(v1, dir));
OriLine nl = new OriLine(v1, cp, Globals.inputLineType);
addLine(nl);
}
// Adds a new OriLine, also searching for intersections with others
// that would cause their mutual division
public void addLine(OriLine inputLine) {
ArrayList<OriLine> crossingLines = new ArrayList<>();
ArrayList<OriLine> tmpLines = new ArrayList<>();
tmpLines.addAll(lines);
// If it already exists, do nothing
for (OriLine line : tmpLines) {
if (GeomUtil.isSameLineSegment(line, inputLine)) {
return;
}
}
// If it intersects other line, devide them
for (OriLine line : tmpLines) {
// Inputted line does not intersect
if (inputLine.type == OriLine.TYPE_NONE && line.type != OriLine.TYPE_NONE) {
continue;
}
Vector2d crossPoint = GeomUtil.getCrossPoint(inputLine, line);
if (crossPoint == null) {
continue;
}
crossingLines.add(line);
lines.remove(line);
if (GeomUtil.Distance(line.p0, crossPoint) > POINT_EPS) {
lines.add(new OriLine(line.p0, crossPoint, line.type));
}
if (GeomUtil.Distance(line.p1, crossPoint) > POINT_EPS) {
lines.add(new OriLine(line.p1, crossPoint, line.type));
}
}
ArrayList<Vector2d> points = new ArrayList<>();
points.add(inputLine.p0);
points.add(inputLine.p1);
for (OriLine line : lines) {
// Dont devide if the type of line is aux is Aux
if (inputLine.type != OriLine.TYPE_NONE && line.type == OriLine.TYPE_NONE) {
continue;
}
// If the intersection is on the end of the line, skip
if (GeomUtil.Distance(inputLine.p0, line.p0) < POINT_EPS) {
continue;
}
if (GeomUtil.Distance(inputLine.p0, line.p1) < POINT_EPS) {
continue;
}
if (GeomUtil.Distance(inputLine.p1, line.p0) < POINT_EPS) {
continue;
}
if (GeomUtil.Distance(inputLine.p1, line.p1) < POINT_EPS) {
continue;
}
if (GeomUtil.DistancePointToSegment(line.p0, inputLine.p0, inputLine.p1) < POINT_EPS) {
points.add(line.p0);
}
if (GeomUtil.DistancePointToSegment(line.p1, inputLine.p0, inputLine.p1) < POINT_EPS) {
points.add(line.p1);
}
// Calculates the intersection
Vector2d crossPoint = GeomUtil.getCrossPoint(inputLine, line);
if (crossPoint != null) {
points.add(crossPoint);
}
}
boolean sortByX = Math.abs(inputLine.p0.x - inputLine.p1.x) > Math.abs(inputLine.p0.y - inputLine.p1.y);
if (sortByX) {
Collections.sort(points, new PointComparatorX());
} else {
Collections.sort(points, new PointComparatorY());
}
Vector2d prePoint = points.get(0);
for (int i = 1; i < points.size(); i++) {
Vector2d p = points.get(i);
if (GeomUtil.Distance(prePoint, p) < POINT_EPS) {
continue;
}
lines.add(new OriLine(prePoint, p, inputLine.type));
prePoint = p;
}
}
public void makeEdges() {
edges.clear();
ArrayList<OriHalfedge> tmpHalfedges = new ArrayList<>();
// Clear all the Halfedges
for (OriFace face : faces) {
for (OriHalfedge he : face.halfedges) {
he.pair = null;
he.edge = null;
tmpHalfedges.add(he);
}
}
// Search the halfedge pair
int heNum = tmpHalfedges.size();
for (int i = 0; i < heNum; i++) {
OriHalfedge he0 = tmpHalfedges.get(i);
if (he0.pair != null) {
continue;
}
for (int j = i + 1; j < heNum; j++) {
OriHalfedge he1 = tmpHalfedges.get(j);
if (he0.vertex == he1.next.vertex && he0.next.vertex == he1.vertex) {
OriEdge edge = new OriEdge();
he0.pair = he1;
he1.pair = he0;
he0.edge = edge;
he1.edge = edge;
edge.sv = he0.vertex;
edge.ev = he1.vertex;
edge.left = he0;
edge.right = he1;
edges.add(edge);
edge.type = OriLine.TYPE_NONE;//OriEdge.TYPE_NONE;
}
}
}
// If the pair wasnt found it should be an edge
for (OriHalfedge he : tmpHalfedges) {
if (he.pair == null) {
OriEdge edge = new OriEdge();
he.edge = edge;
edge.sv = he.vertex;
edge.ev = he.next.vertex;
edge.left = he;
edges.add(edge);
edge.type = OriLine.TYPE_CUT;
}
}
}
public void setCrossLine(OriLine line) {
crossLines.clear();
for (OriFace face : sortedFaces) {
ArrayList<Vector2d> vv = new ArrayList<>();
int crossCount = 0;
for (OriHalfedge he : face.halfedges) {
OriLine l = new OriLine(he.positionForDisplay.x, he.positionForDisplay.y,
he.next.positionForDisplay.x, he.next.positionForDisplay.y, Globals.inputLineType);
double params[] = new double[2];
boolean res = GeomUtil.getCrossPointParam(line.p0, line.p1, l.p0, l.p1, params);
if (res == true && params[0] > -0.001 && params[1] > -0.001 && params[0] < 1.001 && params[1] < 1.001) {
double param = params[1];
crossCount++;
Vector2d crossV = new Vector2d();
crossV.x = (1.0 - param) * he.vertex.preP.x + param * he.next.vertex.preP.x;
crossV.y = (1.0 - param) * he.vertex.preP.y + param * he.next.vertex.preP.y;
boolean isNewPoint = true;
for (Vector2d v2d : vv) {
if (GeomUtil.Distance(v2d, crossV) < 1) {
isNewPoint = false;
break;
}
}
if (isNewPoint) {
vv.add(crossV);
}
}
}
if (vv.size() >= 2) {
crossLines.add(new OriLine(vv.get(0), vv.get(1), Globals.inputLineType));
}
}
}
public void alterLineType(OriLine l) {
int lineTypeFromIndex = ORIPA.mainFrame.uiPanel.alterLine_combo_from.getSelectedIndex();
int lineTypeToIndex = ORIPA.mainFrame.uiPanel.alterLine_combo_to.getSelectedIndex();
if (lineTypeFromIndex == 1 /*M*/ && l.type != OriLine.TYPE_RIDGE) {
return;
}
if (lineTypeFromIndex == 2 /*V*/ && l.type != OriLine.TYPE_VALLEY) {
return;
}
switch (lineTypeToIndex) {
case 0:
l.type = OriLine.TYPE_RIDGE;
break;
case 1:
l.type = OriLine.TYPE_VALLEY;
break;
case 2:
l.type = OriLine.TYPE_NONE;
break;
case 3:
l.type = OriLine.TYPE_CUT;
break;
case 4:
removeLine(l);
break;
case 5: {
if (l.type == OriLine.TYPE_RIDGE) {
l.type = OriLine.TYPE_VALLEY;
} else if (l.type == OriLine.TYPE_VALLEY) {
l.type = OriLine.TYPE_RIDGE;
}
}
}
}
}