Created
September 21, 2015 22:28
-
-
Save agnes1/ee346d353bb10b5e01c1 to your computer and use it in GitHub Desktop.
Draw assemblies of polygons. Supports nudge shape, nudge color, copy-paste, randomize verteces, randomize color, undo, redo, rotate, shrink, grow, and copy-paste color.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.clarke.agnes.polyperfect; | |
import org.treeml.Parser; | |
import java.io.Reader; | |
import java.util.ArrayList; | |
import java.util.Collections; | |
import java.util.List; | |
import java.util.Random; | |
/** | |
* @author agnes.clarke | |
* @since 8/13/2015 | |
*/ | |
public class Model { | |
private List<ModelStep> undo = new ArrayList<>(); | |
private List<ModelStep> redo = new ArrayList<>(); | |
private ModelStep currentStep = new ModelStep(); | |
public void addPoint(ModelStep.Point p) { | |
createStep(); | |
currentStep.currentPoly.points.add(p); | |
} | |
public void newPoly() { | |
if (currentStep.currentPoly.points.size() < 3) { | |
return; | |
} | |
createStep(); | |
currentStep.polies.add(currentStep.currentPoly); | |
currentStep.currentPoly = new ModelStep.Poly(); | |
} | |
public void setColor(double r, double g, double b, double a) { | |
if (currentStep.polies.isEmpty()) { | |
return; | |
} | |
if (currentStep.selectedCompletePoly == null) { | |
return; | |
} | |
createStep(); | |
currentStep.selectedCompletePoly.r = r; | |
currentStep.selectedCompletePoly.g = g; | |
currentStep.selectedCompletePoly.b = b; | |
currentStep.selectedCompletePoly.a = a; | |
} | |
public void copyPoly() { | |
if (currentStep.polies.isEmpty()) { | |
return; | |
} | |
if (currentStep.selectedCompletePoly == null) { | |
return; | |
} | |
createStep(); | |
ModelStep.Poly poly = currentStep.selectedCompletePoly.deepCopy(); | |
poly.nudge(10, 10); | |
currentStep.polies.add(poly); | |
currentStep.selectedCompletePoly = poly; | |
} | |
public void rotate() { | |
if (currentStep.polies.isEmpty()) { | |
return; | |
} | |
if (currentStep.selectedCompletePoly == null) { | |
return; | |
} | |
createStep(); | |
currentStep.selectedCompletePoly.rotate(-20); | |
} | |
public void counterRotate() { | |
if (currentStep.polies.isEmpty()) { | |
return; | |
} | |
if (currentStep.selectedCompletePoly == null) { | |
return; | |
} | |
createStep(); | |
currentStep.selectedCompletePoly.rotate(20); | |
} | |
public void larger() { | |
if (currentStep.polies.isEmpty()) { | |
return; | |
} | |
if (currentStep.selectedCompletePoly == null) { | |
return; | |
} | |
createStep(); | |
currentStep.selectedCompletePoly.resize(1.02); | |
} | |
public void smaller() { | |
if (currentStep.polies.isEmpty()) { | |
return; | |
} | |
if (currentStep.selectedCompletePoly == null) { | |
return; | |
} | |
createStep(); | |
currentStep.selectedCompletePoly.resize(0.98); | |
} | |
public void nudge(double x, double y) { | |
if (currentStep.polies.isEmpty()) { | |
return; | |
} | |
if (currentStep.selectedCompletePoly == null) { | |
return; | |
} | |
createStep(); | |
currentStep.selectedCompletePoly.nudge(x, y); | |
} | |
public void nudgeColor(double r, double g, double b, double a) { | |
if (currentStep.polies.isEmpty()) { | |
return; | |
} | |
if (currentStep.selectedCompletePoly == null) { | |
return; | |
} | |
createStep(); | |
currentStep.selectedCompletePoly.nudgeColor(r, g, b, a); | |
} | |
public void nudgePoints(Random random, boolean big) { | |
if (currentStep.polies.isEmpty()) { | |
return; | |
} | |
if (currentStep.selectedCompletePoly == null) { | |
return; | |
} | |
createStep(); | |
currentStep.selectedCompletePoly.nudgePoints(random, big); | |
} | |
private void createStep() { | |
undo.add(currentStep); | |
currentStep = currentStep.deepCopy(); | |
} | |
public void setScale(double td) { | |
createStep(); | |
currentStep.scale = td; | |
} | |
public double getScale() { | |
return currentStep.scale; | |
} | |
public void undo() { | |
if (undo.size() == 0) { | |
return; | |
} | |
redo.add(currentStep); | |
currentStep = undo.remove(undo.size() - 1); | |
} | |
public void redo() { | |
if (redo.size() == 0) { | |
return; | |
} | |
undo.add(currentStep); | |
currentStep = redo.remove(redo.size() - 1); | |
} | |
public List<ModelStep.Poly> getPolies() { | |
return Collections.unmodifiableList(currentStep.polies); | |
} | |
public ModelStep.Poly getCurrentPoly() { | |
return currentStep.currentPoly; | |
} | |
public ModelStep.Poly getSelectedCompletePoly() { | |
return currentStep.selectedCompletePoly; | |
} | |
public int getCurrentPolySize() { | |
return currentStep.currentPoly.points.size(); | |
} | |
public void nextPoly() { | |
//if (currentStep.polies.isEmpty() || (currentStep.polies.size() == 1 && currentStep.selectedCompletePoly != null)) {return;} | |
//createStep(); | |
currentStep.nextPoly(); | |
} | |
public Parser.Node toNodeTree() { | |
return currentStep.toNodeTree(); | |
} | |
public void load(Reader reader) { | |
undo.clear(); | |
redo.clear(); | |
currentStep = new ModelStep(reader); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.clarke.agnes.polyperfect; | |
import javafx.geometry.Point2D; | |
import javafx.scene.transform.Rotate; | |
import javafx.scene.transform.Transform; | |
import org.treeml.Parser; | |
import java.io.IOException; | |
import java.io.Reader; | |
import java.util.*; | |
/** | |
* @author agnes.clarke | |
* @since 8/4/2015 | |
*/ | |
public class ModelStep { | |
private static final double center = 320; | |
double scale = 10.0; | |
List<Poly> polies = new ArrayList<>(); | |
List<String> animationNames = new ArrayList<>(); | |
Poly selectedCompletePoly = null; | |
Poly currentPoly = new Poly(); | |
public static final Random RANDOM = new Random(); | |
public ModelStep() {} | |
public ModelStep(List<Poly> polies, List<String> animationNames, Poly currentPoly, Poly selectedCompletePoly) { | |
int i = polies.indexOf(selectedCompletePoly); | |
for (Poly poly : polies) { | |
this.polies.add(poly.deepCopy()); | |
} | |
this.selectedCompletePoly = i == -1 ? null : this.polies.get(i); | |
this.animationNames.addAll(animationNames); | |
this.currentPoly = currentPoly.deepCopy(); | |
} | |
public ModelStep(Reader reader) { | |
try { | |
Parser.Node tree = new Parser().parse(reader); | |
Parser.Node node = tree.getNode("poly"); | |
scale = tree.getValueAt("scale", 10.0); | |
currentPoly = new Poly(node, scale); | |
Parser.Node polygons = tree.getNode("polygons"); | |
for (Parser.Node p : polygons.children) { | |
polies.add(new Poly(p, scale)); | |
} | |
} catch (IOException e) { | |
throw new RuntimeException(e); | |
} | |
} | |
ModelStep deepCopy() { | |
return new ModelStep(polies, animationNames, currentPoly, selectedCompletePoly); | |
} | |
public void nextPoly() { | |
boolean choice = false; | |
for (Poly poly : polies) { | |
if (choice) { | |
selectedCompletePoly = poly; | |
return; | |
} | |
choice = poly.equals(selectedCompletePoly); | |
} | |
if ((selectedCompletePoly == null && !polies.isEmpty()) || choice) { | |
selectedCompletePoly = polies.get(0); | |
} | |
} | |
public Parser.Node toNodeTree() { | |
Parser.RootNode root = new Parser.RootNode(); | |
Parser.Node currentPolygon = currentPoly.toNodeTree(0, scale); | |
root.children.add(new Parser.Node("scale", scale)); | |
root.children.add(currentPolygon); | |
Parser.Node polygons = new Parser.Node("polygons", polies.size()); | |
root.children.add(polygons); | |
int counter = 1; | |
for (Poly poly : polies) { | |
Parser.Node node = poly.toNodeTree(counter++, scale); | |
polygons.children.add(node); | |
} | |
return root; | |
} | |
public static class Point{ | |
public double x, y; | |
public Point(double x, double y) {this.x = x; this.y = y;} | |
public Point deepCopy() {return new Point(x,y);} | |
} | |
public static class Poly { | |
public String name; | |
public List<Point> points = new ArrayList<>(); | |
public List<Bone> bones = new ArrayList<>(); | |
public Map<String, Integer> animationSequence = new TreeMap<>(); | |
public double r = RANDOM.nextDouble(); | |
public double g = RANDOM.nextDouble(); | |
public double b = RANDOM.nextDouble(); | |
public double a = 0.1 + RANDOM.nextDouble() / 3.0; | |
public double z = 0; | |
public Poly(){} | |
public Poly(String name, List<Point> points, List<Bone> bones, Map<String, Integer> animationSequence, double r, double g, double b, double a, double z) { | |
this.name = name; | |
for (Point point : points){ | |
this.points.add(point.deepCopy()); | |
} | |
for (Bone bone : bones) { | |
this.bones.add(bone.deepCopy()); | |
} | |
this.animationSequence = new TreeMap<>(animationSequence); | |
this.r = r; | |
this.g = g; | |
this.b = b; | |
this.a = a; | |
this.z = z; | |
} | |
public Poly(Parser.Node node, double scale) { | |
r = node.getValueAt("r"); | |
g = node.getValueAt("g"); | |
b = node.getValueAt("b"); | |
a = node.getValueAt("a"); | |
z = node.getValueAt("z"); | |
List<Double> xPoints = node.getValueAt("xPoints"); | |
List<Double> yPoints = node.getValueAt("yPoints"); | |
if (xPoints == null || yPoints == null) { | |
return; | |
} | |
for (int i = 0; i < xPoints.size(); i++) { | |
try { | |
points.add(new Point(toPx(xPoints.get(i), scale), toPx(yPoints.get(i), scale))); | |
} catch (Exception e) { | |
e.printStackTrace(); | |
} | |
} | |
} | |
public Poly deepCopy() { | |
return new Poly(name, points, bones, animationSequence, r, g, b, a, z); | |
} | |
public Point getCenterPoint() { | |
double minX = Double.MAX_VALUE, maxX = Double.MIN_VALUE, minY = Double.MAX_VALUE, maxY = Double.MIN_VALUE; | |
for (Point p : points) { | |
minX = Math.min(minX, p.x); | |
maxX = Math.max(maxX, p.x); | |
minY = Math.min(minY, p.y); | |
maxY = Math.max(maxY, p.y); | |
} | |
return new Point((minX + maxX) / 2.0, (minY + maxY) / 2.0); | |
} | |
public void rotate(double angle) { | |
Point center = getCenterPoint(); | |
Rotate rotate = Transform.rotate(Math.toRadians(angle), center.x, center.y); | |
for (Point p : points) { | |
Point2D point2D = rotate.transform(p.x, p.y); | |
p.x = point2D.getX(); | |
p.y = point2D.getY(); | |
} | |
} | |
public void resize(double scale) { | |
Point center = getCenterPoint(); | |
for (Point p : points) { | |
double deltaX = (p.x - center.x) * scale; | |
double deltaY = (p.y - center.y) * scale; | |
p.x = deltaX + center.x; | |
p.y = deltaY + center.y; | |
} | |
} | |
public void nudge(double x, double y) { | |
for (Point p : points) { | |
p.x = p.x + x; | |
p.y = p.y + y; | |
} | |
} | |
public int pointsSize() { | |
return points.size(); | |
} | |
public Point getPoints(int i) { | |
return points.get(i); | |
} | |
public double[] xPoints() { | |
double[] result = new double[points.size()]; | |
int i = 0; | |
for (Point point : points) { | |
result[i++] = point.x; | |
} | |
return result; | |
} | |
public double[] yPoints() { | |
double[] result = new double[points.size()]; | |
int i = 0; | |
for (Point point : points) { | |
result[i++] = point.y; | |
} | |
return result; | |
} | |
public void nudgeColor(double r, double g, double b, double a) { | |
this.r += r; | |
this.g += g; | |
this.b += b; | |
this.a += a; | |
this.r = Math.min(1.0, Math.max(0.0, this.r)); | |
this.g = Math.min(1.0, Math.max(0.0, this.g)); | |
this.b = Math.min(1.0, Math.max(0.0, this.b)); | |
this.a = Math.min(1.0, Math.max(0.0, this.a)); | |
} | |
public List<Double> xPointsList(double scale) { | |
List<Double> result = new ArrayList<>(points.size()); | |
for (double x : xPoints()) { | |
x = (x - center) / center / 2.0 * scale; | |
result.add(x); | |
} | |
return result; | |
} | |
public double toPx(double v, double scale) { | |
return ((v * center) * 2.0 / scale) + center; | |
} | |
public List<Double> yPointsList(double scale) { | |
List<Double> result = new ArrayList<>(points.size()); | |
for (double y : yPoints()) { | |
y = (y - center) / center / 2.0 * scale; | |
result.add(y); | |
} | |
return result; | |
} | |
public void nudgePoints(Random random, boolean big) { | |
double factor = big ? 5.0 : 1.0; | |
for (Point point : points) { | |
point.x += (random.nextBoolean() ? -2.0 : 2.0) * random.nextDouble() * factor; | |
point.y += (random.nextBoolean() ? -2.0 : 2.0) * random.nextDouble() * factor; | |
} | |
} | |
public Parser.Node toNodeTree(int counter, double scale) { | |
Parser.Node node = new Parser.Node("poly", counter); | |
node.children.add(new Parser.Node("r", r)); | |
node.children.add(new Parser.Node("g", g)); | |
node.children.add(new Parser.Node("b", b)); | |
node.children.add(new Parser.Node("a", a)); | |
node.children.add(new Parser.Node("xPoints", xPointsList(scale))); | |
node.children.add(new Parser.Node("yPoints", yPointsList(scale))); | |
node.children.add(new Parser.Node("z", z)); | |
return node; | |
} | |
} | |
public static class Bone { | |
public double x; | |
public double y; | |
public Bone(double x, double y) { | |
this.x = x; | |
this.y = y; | |
} | |
public Bone deepCopy() { | |
return new Bone(x, y); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package org.treeml; | |
import java.io.BufferedReader; | |
import java.io.IOException; | |
import java.io.Reader; | |
import java.text.DecimalFormat; | |
import java.text.DecimalFormatSymbols; | |
import java.util.ArrayList; | |
import java.util.Arrays; | |
import java.util.List; | |
import java.util.Locale; | |
/** | |
* Parses a tab-indented file into a tree of Nodes. | |
* @author agnes.clarke | |
*/ | |
public class Parser { | |
public static final String NADA = "nada", NULL = "null", TRUE = "true", FALSE = "false"; | |
private int indentOfLastLine = 0; | |
public static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("0", DecimalFormatSymbols.getInstance(Locale.ENGLISH)); | |
public static final DecimalFormat LONG_FORMAT = new DecimalFormat("0", DecimalFormatSymbols.getInstance(Locale.ENGLISH)); | |
static { | |
DECIMAL_FORMAT.setMaximumFractionDigits(18); | |
DECIMAL_FORMAT.setMinimumFractionDigits(1); | |
LONG_FORMAT.setMaximumFractionDigits(0); | |
} | |
public Node parse(Reader input) throws IOException { | |
int lineNumber = 1; | |
List<Node> nodeStack = new ArrayList<Node>(); | |
root = new RootNode(); | |
nodeStack.add(root); | |
BufferedReader reader = new BufferedReader(input); | |
String line = reader.readLine(); | |
while (line != null) { | |
doLine(line, nodeStack, lineNumber++); | |
line = reader.readLine(); | |
} | |
return root; | |
} | |
private final List<String> valueList = new ArrayList<>(); | |
private RootNode root; | |
private void doLine(String line, List<Node> nodeStack, int lineNumber) { | |
if (lineNumber == 1 && line.startsWith("#")) { | |
root.value = line.substring(1).trim(); | |
return; | |
} | |
if (lineNumber == 2 && line.startsWith("#")) { | |
root.requires = line.substring(1).trim(); | |
return; | |
} | |
valueList.clear(); | |
Node newNode = new Node(null, null); | |
int stackSize = nodeStack.size(); | |
int stackPointer = 0; // node to append to | |
boolean startOfLine = true; | |
boolean insideValue = false; | |
boolean insideString = false; | |
boolean valueSeparatedByWhitespace = false; | |
StringBuilder nameOrValue = new StringBuilder(); | |
for (int index = 0; index < line.length(); index++) { | |
char c = line.charAt(index); | |
if (c == '\t' && startOfLine) { | |
stackPointer++; | |
} else { | |
if (startOfLine) { | |
if (c == '/' && nextCharEquals(line, index, '/')) { | |
// disregard line - it is a comment | |
return; | |
} | |
startOfLine = false; | |
// drop nodes higher than current stackPointer | |
for (int i = stackSize - 1; i > stackPointer; i--) { | |
nodeStack.remove(i); | |
} | |
if (stackPointer > indentOfLastLine + 1) { | |
throw new RuntimeException("Line " + lineNumber + ": illegal indentation"); | |
} else { | |
indentOfLastLine = stackPointer; | |
} | |
nodeStack.get(stackPointer).children.add(newNode); | |
nodeStack.add(newNode); | |
} | |
if (!insideString && c == '/' && nextCharEquals(line, index, '/')) { | |
break; | |
} | |
if (!insideString && (c == ':' || c == ',')) { | |
if (newNode.name != null) { | |
// add values to list | |
valueList.add(nameOrValue.toString()); | |
} else { | |
newNode.name = nameOrValue.toString(); | |
} | |
insideValue = false; | |
nameOrValue = new StringBuilder(); | |
valueSeparatedByWhitespace = false; | |
} else if (!insideString && (c == ' ' || c == '\t')) { | |
if (insideValue && !insideString) { | |
valueSeparatedByWhitespace = true; | |
} | |
} else { | |
if (c == '"') { | |
if (!insideString && insideValue) { | |
throw new RuntimeException("Line " + lineNumber + ", char " + index | |
+ ": Illegal quote"); | |
} | |
insideString = !insideString; | |
insideValue = true; | |
} else { | |
insideValue = true; | |
if (valueSeparatedByWhitespace) { | |
throw new RuntimeException("Line " + lineNumber + ", char " + index | |
+ ": Illegal whitespace"); | |
} | |
if (insideString && (c == '\\' && nextCharEquals(line, index, 'n'))) { | |
nameOrValue.append('\n'); | |
index++; | |
} else if (insideString && (c == '\\' && nextCharEquals(line, index, 'r'))) { | |
nameOrValue.append('\r'); | |
index++; | |
} else if (insideString && (c == '\\' && nextCharEquals(line, index, '"'))) { | |
nameOrValue.append('"'); | |
index++; | |
} else if (insideString && (c == '\\' && nextCharEquals(line, index, '\\'))) { | |
nameOrValue.append('\\'); | |
index++; | |
} else { | |
nameOrValue.append(c); | |
} | |
} | |
} | |
} | |
} | |
Object value; | |
if (valueList.isEmpty()) { | |
value = nameOrValue.toString(); | |
} else { | |
valueList.add(nameOrValue.toString()); | |
value = new ArrayList<>((valueList)); | |
} | |
newNode.value = value; | |
typifyNode(newNode, nodeStack); | |
} | |
// Get the types of values right, nada the nada elements | |
private void typifyNode(Node newNode, List<Node> nodeStack) { | |
Object val = newNode.value; | |
if (NADA.equals(val)) { | |
dropNewNode(newNode, nodeStack); | |
} else if ("".equals(val)) { | |
newNode.value = null; | |
} else if (!(val instanceof List<?>)) { | |
newNode.value = typifyValue(null, val); | |
} else { | |
@SuppressWarnings("unchecked") | |
List<Object> list = (List<Object>) val; | |
if (list.removeAll(Arrays.asList(NADA)) && list.isEmpty()) { | |
dropNewNode(newNode, nodeStack); | |
} else { | |
List<Object> newList = new ArrayList<>(); | |
for (int i = 0; i < list.size(); i++) { | |
Object untypified = list.get(i); | |
typifyValue(newList, untypified); | |
} | |
newNode.value = newList; | |
} | |
} | |
} | |
private Object typifyValue(List<Object> valueList, Object untypified) { | |
if (NULL.equals(untypified)) { | |
return setValue(null, valueList); | |
} else if (TRUE.equals(untypified)) { | |
return setValue(true, valueList); | |
} else if (FALSE.equals(untypified)) { | |
return setValue(false, valueList); | |
} else if (untypified.toString().matches("[+-]?[0-9]+")) { | |
return setValue(Long.parseLong(untypified.toString()), valueList); | |
} else if (untypified.toString().matches("[+-]?[0-9]+(\\.[0-9]+)")) { | |
return setValue(Double.parseDouble(untypified.toString()), valueList); | |
} | |
return setValue(untypified.toString(), valueList); | |
} | |
private Object setValue(Object value, List<Object> valueList) { | |
if (valueList != null) { | |
valueList.add(value); | |
} | |
return value; | |
} | |
private void dropNewNode(Node newNode, List<Node> nodeStack) { | |
nodeStack.remove(newNode); | |
nodeStack.get(nodeStack.size() - 1).children.remove(newNode); | |
} | |
private boolean nextCharEquals(String line, int index, char... cs) { | |
boolean result = true; | |
int max = line.length(); | |
for (int i = 0; i < cs.length; i++) { | |
char c = cs[i]; | |
//noinspection StatementWithEmptyBody | |
if (index + 1 + i < max && line.charAt(index + 1 + i) == c) { | |
// match, do nothing | |
} else { | |
result = false; | |
} | |
} | |
return result; | |
} | |
public static class Node { | |
public String name; | |
public Object value; | |
public List<Node> children = new ArrayList<>(); | |
public Node(String name, Object value) { | |
this.name = name; | |
this.value = value; | |
} | |
public String toString() { | |
StringBuilder sb = new StringBuilder(); | |
toStringHelper(sb, this, ""); | |
return sb.toString(); | |
} | |
private void toStringHelper(StringBuilder sb, Node node, String indent) { | |
sb.append(indent).append(node.name).append("---").append(node.value).append("\r\n"); | |
for (Node child : node.children) { | |
toStringHelper(sb, child, indent + " "); | |
} | |
} | |
public List<Node> getNodes(String name) { | |
List<Node> result = new ArrayList<>(); | |
for (int i = 0; i < children.size(); i++) { | |
if (name.equals(children.get(i).name)) { | |
result.add(children.get(i)); | |
} | |
} | |
return result; | |
} | |
public <T> T getValueAt(String name, T defaultValue) { | |
T t = getValueAt(name); | |
return t == null ? defaultValue : t; | |
} | |
public <T> T getValueAt(String name) { | |
String[] steps = name.split("\\."); | |
Node node = this; | |
for (String step : steps) { | |
List<Node> nodes = node.getNodes(step); | |
if (nodes.isEmpty()) return null; | |
node = nodes.get(0); | |
} | |
//noinspection unchecked | |
return (T) node.value; | |
} | |
public Node copy() { | |
return new Node(name, value); | |
} | |
public Node getNode(String nameForNode) { | |
for (int i = 0; i < children.size(); i++) { | |
if (nameForNode.equals(children.get(i).name)) { | |
return children.get(i); | |
} | |
} | |
return null; | |
} | |
public String toTreeML() { | |
StringBuilder sb = new StringBuilder(); | |
toTreeMLImpl(sb, 0); | |
return sb.toString(); | |
} | |
public void toTreeMLImpl(StringBuilder sb, int indent) { | |
for (int i = 0; i < indent; i++) { | |
sb.append('\t'); | |
} | |
sb.append(this.name).append(" : ").append(val(this.value)).append('\n'); | |
for (Node child : this.children) { | |
child.toTreeMLImpl(sb, indent + 1); | |
} | |
} | |
private String val(Object v) { | |
if (v instanceof String) { | |
String s = (String) v; | |
s = s.replace("\r", "\\r").replace("\n", "\\n").replace("\"", "\"\""); | |
if (s.contains(" ") || !s.equals(v)) { | |
return '"' + s + '"'; | |
} else { | |
return (String)v; | |
} | |
} else if (v instanceof Double) { | |
Double d = (Double) v; | |
if (Math.abs(d) < 0.001 || Math.abs(d) > 999999) { | |
return (DECIMAL_FORMAT.format(v)); | |
} | |
} else if (v instanceof Long) { | |
Long lo = (Long) v; | |
if (Math.abs(lo) > 999999) { | |
return (LONG_FORMAT.format(lo)); | |
} | |
} else if (v instanceof List<?>) { | |
boolean b = true; | |
StringBuilder sb2 = new StringBuilder(); | |
for (Object o : (List<?>)v) { | |
if (b) { | |
b = false; | |
} else { | |
sb2.append(", "); | |
} | |
sb2.append(val(o)); | |
} | |
return sb2.toString(); | |
} | |
return String.valueOf(v); | |
} | |
} | |
public static class RootNode extends Node { | |
public String requires; | |
public RootNode() { | |
super("root", null); | |
} | |
public String toString() { | |
return requires == null ? super.toString() : requires + "\r\n" + super.toString(); | |
} | |
public Node copy() { | |
RootNode rootNode = new RootNode(); | |
rootNode.requires = requires; | |
return rootNode; | |
} | |
public String toTreeML() { | |
StringBuilder sb = new StringBuilder(); | |
int indent = 0; | |
for (Node child : children) { | |
child.toTreeMLImpl(sb, indent); | |
} | |
return sb.toString(); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.clarke.agnes.polyperfect; | |
import javafx.application.Application; | |
import javafx.geometry.Insets; | |
import javafx.scene.Scene; | |
import javafx.scene.canvas.Canvas; | |
import javafx.scene.canvas.GraphicsContext; | |
import javafx.scene.input.KeyEvent; | |
import javafx.scene.input.MouseButton; | |
import javafx.scene.input.MouseEvent; | |
import javafx.scene.layout.GridPane; | |
import javafx.scene.paint.Color; | |
import javafx.scene.text.Text; | |
import javafx.scene.control.TextField; | |
import javafx.stage.FileChooser; | |
import javafx.stage.Stage; | |
import java.io.*; | |
import java.util.ArrayList; | |
import java.util.List; | |
import java.util.Random; | |
//Credit: https://docs.oracle.com/javafx/2/canvas/jfxpub-canvas.htm | |
public class PolyMain extends Application { | |
private static final int DIM = 640; | |
//private List<Point> points; | |
//private List<List<Point>> donePolies = new ArrayList<>(); | |
private final List<Boolean> gridFlag = new ArrayList<>(1); | |
private final Random random = new Random(); | |
Model model = new Model(); | |
private double copyR, copyG, copyB, copyA; | |
public static void main(String[] args) throws InterruptedException { | |
launch(args); | |
} | |
@Override | |
public void start(Stage primaryStage) { | |
gridFlag.add(true); | |
GridPane grid = new GridPane(); | |
grid.setHgap(10); | |
grid.setVgap(10); | |
grid.setPadding(new Insets(10, 10, 10, 10)); | |
primaryStage.setTitle("PolyPerfect 5.1"); | |
Canvas canvas = new Canvas(DIM, DIM); | |
grid.add(canvas, 5, 0, 15, 15); | |
Text scaleTxt = new Text("Scale: "); | |
grid.add(scaleTxt, 0, 1, 1, 1); | |
final TextField scaleTF = new TextField(); | |
grid.add(scaleTF, 1, 1, 3, 1); | |
scaleTF.setText("" + model.getScale()); | |
scaleTF.setOnAction(event -> { | |
String t = scaleTF.getText(); | |
double td = Double.parseDouble(t); | |
model.setScale(td); | |
}); | |
Text rtxt = new Text("Color: "); | |
grid.add(rtxt, 0, 0, 1, 1); | |
final TextField rgba = new TextField(); | |
grid.add(rgba, 1, 0, 3, 1); | |
final TextField colorDisplay = new TextField(); | |
final GraphicsContext gc = canvas.getGraphicsContext2D(); | |
rgba.setOnAction(event -> { | |
String[] color = rgba.getText().split(","); | |
int v0 = (int) (255 * Double.parseDouble(color[0])); | |
int v1 = (int) (255 * Double.parseDouble(color[1])); | |
int v2 = (int) (255 * Double.parseDouble(color[2])); | |
double v3 = Double.parseDouble(color[3]); | |
colorDisplay.setStyle("-fx-background-color:rgba(" + String.format("%s,%s,%s,%.2f", v0, v1, v2, v3) + ")"); | |
}); | |
colorDisplay.setOnAction(event -> { | |
try { | |
String[] color = rgba.getText().split(","); | |
model.setColor( | |
Double.parseDouble(color[0]), | |
Double.parseDouble(color[1]), | |
Double.parseDouble(color[2]), | |
Double.parseDouble(color[3])); | |
drawShapes(gc); | |
colorDisplay.getParent().requestFocus(); | |
} catch (Throwable ignore) { | |
} | |
}); | |
//grid.add(dummy, 4, 0, 1, 1); | |
grid.add(colorDisplay, 4, 0, 1, 1); | |
canvas.setFocusTraversable(true); | |
canvas.addEventFilter(MouseEvent.ANY, (e) -> canvas.requestFocus()); | |
primaryStage.addEventHandler(KeyEvent.KEY_TYPED, event -> { | |
String character = event.getCharacter(); | |
if ("p".equals(character) && model.getCurrentPolySize() > 2) { | |
model.newPoly(); | |
} | |
if ("]".equals(character)) { | |
model.nextPoly(); | |
} | |
if ("z".equals(character)) { | |
model.undo(); | |
} | |
if ("y".equals(character)) { | |
model.redo(); | |
} | |
if ("w".equals(character)) { | |
model.nudge(0, -2); | |
} | |
if ("a".equals(character)) { | |
model.nudge(-2, 0); | |
} | |
if ("s".equals(character)) { | |
model.nudge(0, 2); | |
} | |
if ("d".equals(character)) { | |
model.nudge(2, 0); | |
} | |
if ("<".equals(character)) { | |
model.rotate(); | |
} | |
if (">".equals(character)) { | |
model.counterRotate(); | |
} | |
if ("+".equals(character)) { | |
model.larger(); | |
} | |
if ("-".equals(character)) { | |
model.smaller(); | |
} | |
if ("r".equals(character)) { | |
model.nudgeColor(0.05, 0, 0, 0); | |
} | |
if ("R".equals(character)) { | |
model.nudgeColor(-0.05, 0, 0, 0); | |
} | |
if ("g".equals(character)) { | |
model.nudgeColor(0, 0.05, 0, 0); | |
} | |
if ("G".equals(character)) { | |
model.nudgeColor(0, -0.05, 0, 0); | |
} | |
if ("b".equals(character)) { | |
model.nudgeColor(0, 0, 0.05, 0); | |
} | |
if ("B".equals(character)) { | |
model.nudgeColor(0, 0, -0.05, 0); | |
} | |
if ("t".equals(character)) { | |
model.nudgeColor(0, 0, 0, 0.05); | |
} | |
if ("T".equals(character)) { | |
model.nudgeColor(0, 0, 0, -0.05); | |
} | |
if ("o".equals(character)) { | |
model.nudgePoints(random, false); | |
} | |
if ("O".equals(character)) { | |
model.nudgePoints(random, true); | |
} | |
if ("x".equals(character)) { | |
if (model.getSelectedCompletePoly() != null) { | |
copyR = model.getSelectedCompletePoly().r; | |
copyG = model.getSelectedCompletePoly().g; | |
copyB = model.getSelectedCompletePoly().b; | |
copyA = model.getSelectedCompletePoly().a; | |
} | |
} | |
if ("X".equals(character)) { | |
model.setColor(copyR, copyG, copyB, copyA); | |
} | |
if ("i".equals(character)) { | |
model.nudgeColor( | |
random.nextBoolean() ? -0.01 : +0.01, | |
random.nextBoolean() ? -0.01 : +0.01, | |
random.nextBoolean() ? -0.01 : +0.01, | |
0 | |
); | |
} | |
if ("I".equals(character)) { | |
model.nudgeColor( | |
random.nextBoolean() ? -0.05 : +0.05, | |
random.nextBoolean() ? -0.05 : +0.05, | |
random.nextBoolean() ? -0.05 : +0.05, | |
0 | |
); | |
} | |
if ("c".equals(character)) { | |
model.copyPoly(); | |
} | |
if ("S".equals(character)) { | |
FileChooser fileChooser = new FileChooser(); | |
fileChooser.setTitle("Save to PP5 format"); | |
File saveFile = fileChooser.showSaveDialog(primaryStage); | |
if (saveFile != null) { | |
try { | |
FileWriter writer = new FileWriter(saveFile); | |
writer.write(model.toNodeTree().toTreeML()); | |
writer.flush(); | |
writer.close(); | |
} catch (Exception e) { | |
e.printStackTrace(); | |
} | |
} | |
} | |
if ("Q".equals(character)) { | |
FileChooser fileChooser = new FileChooser(); | |
fileChooser.setTitle("Load PP5 format"); | |
File savedFile = fileChooser.showOpenDialog(primaryStage); | |
if (savedFile != null) { | |
try { | |
FileReader reader = new FileReader(savedFile); | |
model.load(reader); | |
reader.close(); | |
} catch (Exception e) { | |
e.printStackTrace(); | |
} | |
} | |
} | |
ModelStep.Poly p = model.getSelectedCompletePoly(); | |
if (p != null) { | |
rgba.setText(String.format("%.3f,%.3f,%.3f,%.3f", p.r, p.g, p.b, p.a)); | |
colorDisplay.setStyle("-fx-background-color:rgba(" + String.format("%.0f,%.0f,%.0f,%.2f", p.r * 255, p.g * 255, p.b * 255, p.a) + ")"); | |
} | |
drawShapes(gc); | |
scaleTF.setText("" + model.getScale()); | |
}); | |
canvas.setOnMouseClicked(e -> { | |
if (e.getButton().equals(MouseButton.PRIMARY) && e.isStillSincePress()) { | |
int x = (int) e.getX(); | |
int y = (int) e.getY(); | |
model.addPoint(new ModelStep.Point(x, y)); | |
} else { | |
gridFlag.add(0, !gridFlag.get(0)); | |
gridFlag.remove(1); | |
} | |
drawShapes(gc); | |
}); | |
drawShapes(gc); | |
//root.getChildren().add(canvas); | |
primaryStage.setScene(new Scene(grid)); | |
primaryStage.show(); | |
} | |
private void drawShapes(GraphicsContext gc) { | |
gc.setFill(Color.WHITE); | |
gc.setStroke(Color.BLACK); | |
gc.setLineWidth(1); | |
gc.fillRect(0, 0, 1920, 1080); | |
gc.strokeRect(0, 0, 640, 640); | |
//grid | |
if (gridFlag.get(0)) { | |
gc.setFill(Color.AQUAMARINE); | |
gc.setStroke(Color.AQUAMARINE); | |
gc.setLineWidth(1); | |
for (int x = 32; x < DIM; x += 32) { | |
gc.strokeLine(x, 0, x, DIM); | |
} | |
for (int y = 32; y < DIM; y += 32) { | |
gc.strokeLine(0, y, DIM, y); | |
} | |
} | |
int r = 4; | |
drawPoint(320, 320, gc, r); | |
//draw DonePolies | |
for (ModelStep.Poly poly : model.getPolies()) { | |
gc.setFill(new Color(poly.r, poly.g, poly.b, poly.a)); | |
gc.setStroke(new Color(poly.r, poly.g, poly.b, poly.a)); | |
double[] xPoints = poly.xPoints(); | |
double[] yPoints = poly.yPoints(); | |
int nPoints = poly.pointsSize(); | |
gc.fillPolygon(xPoints, yPoints, nPoints); | |
} | |
//highlight selected complete poly | |
if (null != model.getSelectedCompletePoly()) { | |
gc.setLineWidth(1); | |
ModelStep.Poly poly = model.getSelectedCompletePoly(); | |
gc.setStroke(Color.BLACK); | |
double[] xPoints = poly.xPoints(); | |
double[] yPoints = poly.yPoints(); | |
int nPoints = poly.pointsSize(); | |
gc.strokePolygon(xPoints, yPoints, nPoints); | |
gc.setFill(new Color(1, 0.5, 0.5, 1)); | |
ModelStep.Point p = model.getSelectedCompletePoly().getCenterPoint(); | |
drawPoint((int) p.x, (int) p.y, gc, 2); | |
} | |
if (model.getCurrentPolySize() == 0) { | |
return; | |
} | |
if (model.getCurrentPolySize() == 1) { | |
gc.setFill(Color.AQUAMARINE); | |
gc.setStroke(Color.AQUAMARINE); | |
gc.setLineWidth(1); | |
ModelStep.Point p1 = model.getCurrentPoly().getPoints(0); | |
drawPoint((int) p1.x, (int) p1.y, gc, r * 2); | |
return; | |
} | |
for (int i = 1; i < model.getCurrentPolySize(); i++) { | |
ModelStep.Point p1 = model.getCurrentPoly().getPoints(i - 1); | |
ModelStep.Point p2 = model.getCurrentPoly().getPoints(i); | |
drawLine(p1.x, p1.y, p2.x, p2.y, gc, r, false); | |
} | |
ModelStep.Point p1 = model.getCurrentPoly().getPoints(0); | |
ModelStep.Point p2 = model.getCurrentPoly().getPoints(model.getCurrentPolySize() - 1); | |
drawLine(p1.x, p1.y, p2.x, p2.y, gc, r, true); | |
} | |
private void drawLine(double x, double y, double x2, double y2, GraphicsContext gc, int r, boolean closer) { | |
if (closer) { | |
gc.setFill(Color.AQUAMARINE); | |
gc.setStroke(Color.AQUAMARINE); | |
gc.setLineWidth(3); | |
} else { | |
gc.setFill(Color.GREY); | |
gc.setStroke(Color.GREY); | |
gc.setLineWidth(3); | |
} | |
gc.strokeLine(x, y, x2, y2); | |
if (closer) { | |
gc.setFill(Color.AQUAMARINE); | |
gc.setStroke(Color.AQUAMARINE); | |
gc.setLineWidth(3); | |
drawPoint((int) x, (int) y, gc, r * 2); | |
drawPoint((int) x2, (int) y2, gc, r * 2); | |
} else { | |
gc.setFill(Color.BLACK); | |
gc.setStroke(Color.BLACK); | |
gc.setLineWidth(3); | |
drawPoint((int) x, (int) y, gc, r); | |
drawPoint((int) x2, (int) y2, gc, r); | |
} | |
} | |
private void drawPoint(int x, int y, GraphicsContext gc, int r) { | |
gc.fillOval(x - r, y - r, 2 * r, 2 * r); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment