eu.mihosoft.vrl.v3d.TextExtrude Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of JavaCad Show documentation
Show all versions of JavaCad Show documentation
A Java based CSG Cad library
package eu.mihosoft.vrl.v3d;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import javafx.scene.shape.ClosePath;
import javafx.scene.shape.CubicCurveTo;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.scene.shape.PathElement;
import javafx.scene.shape.QuadCurveTo;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Shape;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.scene.paint.Color;
import javafx.scene.shape.Path;
import javafx.scene.shape.PathElement;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.CubicCurveTo;
import javafx.scene.shape.QuadCurveTo;
import javafx.scene.shape.ClosePath;
import javafx.scene.text.Text;
import eu.mihosoft.vrl.v3d.Vector3d;
import java.util.*;
// TODO: Auto-generated Javadoc
/**
* The Class Text.
*/
@SuppressWarnings("restriction")
public class TextExtrude {
private static final String default_font = "FreeSerif";
private final static int POINTS_CURVE = 10;
private final String text;
// private List points;
// HashSet unique = new HashSet();
private Vector3d p0;
private final List polis = new ArrayList<>();
ArrayList sections = new ArrayList();
ArrayList holes = new ArrayList();
private double dir;
class LineSegment {
/*
* Given one single character in terms of Path, LineSegment stores a list of
* points that define the exterior of one of its polygons (!isHole). It can
* contain reference to one or several holes inside this polygon. Or it can
* define the perimeter of a hole (isHole), with no more holes inside.
*/
private boolean hole;
private List points;
private Path path;
private Vector3d origen;
private List holes = new ArrayList<>();
private String letter;
public LineSegment(String text) {
letter = text;
}
public String getLetter() {
return letter;
}
public void setLetter(String letter) {
this.letter = letter;
}
public boolean isHole() {
return hole;
}
public void setHole(boolean isHole) {
this.hole = isHole;
}
public List getPoints() {
return points;
}
public void setPoints(List points) {
this.points = points;
}
public Path getPath() {
return path;
}
public void setPath(Path path) {
this.path = path;
}
public Vector3d getOrigen() {
return origen;
}
public void setOrigen(Vector3d origen) {
this.origen = origen;
}
public List getHoles() {
return holes;
}
public void setHoles(List holes) {
this.holes = holes;
}
public void addHole(LineSegment hole) {
holes.add(hole);
}
@Override
public String toString() {
return "Poly{" + "points=" + points + ", path=" + path + ", origen=" + origen + ", holes=" + holes + '}';
}
}
private TextExtrude(String text, Font font, double dir) {
if (dir <= 0)
throw new NumberFormatException("length can not be negative");
this.dir = dir;
// points = new ArrayList<>();
this.text = text;
Text textNode = new Text(text);
textNode.setFont(font);
// Convert Text to Path
Path subtract = (Path) (Shape.subtract(textNode, new Rectangle(0, 0)));
List> outlines = extractOutlines(subtract);
double zOff = 0;
for (List points : outlines) {
boolean hole = Extrude.isCCW(Polygon.fromPoints(points));
CSG newLetter = Extrude.points(new Vector3d(0, 0, dir), points).movez(zOff);
if (!hole)
sections.add(newLetter);
else
holes.add(newLetter);
// zOff+=dir;
}
// // Convert Path elements into lists of points defining the perimeter
// // (exterior or interior)
// subtract.getElements().forEach(this::getPoints);
for (int i = 0; i < sections.size(); i++) {
for (CSG h : holes) {
try {
if (sections.get(i).touching(h)) {
// println "Hole found "
CSG nl = sections.get(i).difference(h);
sections.set(i, nl);
}
} catch (Exception e) {
}
}
}
}
/**
* Extrudes the specified path (convex or concave polygon without holes or
* intersections, specified in CCW) into the specified direction.
*
* @param dir direction of extrusion
* @param text text
* @param font font configuration of the text
*
* @return a CSG object that consists of the extruded polygon
*/
@SuppressWarnings("restriction")
public static ArrayList text(double dir, String text, Font font) {
TextExtrude te = new TextExtrude(text, font, dir);
return te.sections;
}
public List getLineSegment() {
return polis;
}
public List getOffset() {
return polis.stream().sorted((p1, p2) -> (int) (p1.getOrigen().x - p2.getOrigen().x))
.map(LineSegment::getOrigen).collect(Collectors.toList());
}
// Below is AI slop
private static final double CURVE_SEGMENTS = 3; // Number of segments to approximate curves
private static final double POINT_EPSILON = 0.0001; // Distance threshold for considering points equal
/**
* Converts a JavaFX Text object into a list of cleaned vector lists
* representing the outlines
*/
public static List> extractOutlines(Path text) {
List> rawOutlines = extractRawOutlines(text);
List> cleanedOutlines = new ArrayList<>();
for (List outline : rawOutlines) {
List cleaned = cleanOutline(outline);
if (cleaned.size() >= 3) { // Only keep outlines with at least 3 points
cleanedOutlines.add(cleaned);
}
}
return cleanedOutlines;
}
/**
* Initial extraction of outlines from text
*/
private static List> extractRawOutlines(Path textPath) {
List> allOutlines = new ArrayList<>();
List currentPath = new ArrayList<>();
Vector3d lastPoint = null;
for (PathElement element : textPath.getElements()) {
if (element instanceof MoveTo) {
if (!currentPath.isEmpty()) {
allOutlines.add(new ArrayList<>(currentPath));
currentPath.clear();
}
MoveTo move = (MoveTo) element;
lastPoint = Vector3d.xyz(move.getX(), move.getY(), 0);
currentPath.add(lastPoint);
} else if (element instanceof LineTo) {
LineTo line = (LineTo) element;
lastPoint = Vector3d.xyz(line.getX(), line.getY(), 0);
currentPath.add(lastPoint);
} else if (element instanceof CubicCurveTo) {
CubicCurveTo curve = (CubicCurveTo) element;
List curvePoints = approximateCubicCurve(lastPoint,
Vector3d.xyz(curve.getControlX1(), curve.getControlY1(), 0),
Vector3d.xyz(curve.getControlX2(), curve.getControlY2(), 0),
Vector3d.xyz(curve.getX(), curve.getY(), 0));
currentPath.addAll(curvePoints);
lastPoint = curvePoints.get(curvePoints.size() - 1);
} else if (element instanceof QuadCurveTo) {
QuadCurveTo curve = (QuadCurveTo) element;
List curvePoints = approximateQuadCurve(lastPoint,
Vector3d.xyz(curve.getControlX(), curve.getControlY(), 0),
Vector3d.xyz(curve.getX(), curve.getY(), 0));
currentPath.addAll(curvePoints);
lastPoint = curvePoints.get(curvePoints.size() - 1);
}
}
if (!currentPath.isEmpty()) {
allOutlines.add(currentPath);
}
return allOutlines;
}
/**
* Clean an outline by removing duplicate points and ensuring proper closure
*/
private static List cleanOutline(List outline) {
if (outline.isEmpty())
return outline;
List cleaned = new ArrayList<>();
Vector3d prevPoint = null;
// Process all points
for (Vector3d point : outline) {
if (prevPoint == null || !isNearlyEqual(prevPoint, point)) {
// Only add point if it's significantly different from the previous point
cleaned.add(point);
prevPoint = point;
}
}
// Remove redundant points that form zero-area triangles
return removeRedundantPoints(cleaned);
}
/**
* Remove points that form zero-area triangles with their neighbors
*/
private static List removeRedundantPoints(List points) {
if (points.size() < 3)
return points;
List result = new ArrayList<>();
int size = points.size();
for (int i = 0; i < size; i++) {
Vector3d curr = points.get(i);
result.add(curr);
}
return result;
}
private static boolean isNearlyEqual(Vector3d v1, Vector3d v2) {
return v1.minus(v2).length() < POINT_EPSILON;
}
// Bezier curve methods remain the same
private static List approximateCubicCurve(Vector3d start, Vector3d control1, Vector3d control2,
Vector3d end) {
List points = new ArrayList<>();
for (double i = 1; i <= CURVE_SEGMENTS; i+=1) {
double t = (double) i / CURVE_SEGMENTS;
double x = cubicBezier(start.x, control1.x, control2.x, end.x, t);
double y = cubicBezier(start.y, control1.y, control2.y, end.y, t);
points.add(Vector3d.xyz(x, y, 0));
}
return points;
}
private static List approximateQuadCurve(Vector3d start, Vector3d control, Vector3d end) {
List points = new ArrayList<>();
for (double i = 1; i <= CURVE_SEGMENTS; i+=1) {
double t = (double) i / CURVE_SEGMENTS;
double x = quadBezier(start.x, control.x, end.x, t);
double y = quadBezier(start.y, control.y, end.y, t);
points.add(Vector3d.xyz(x, y, 0));
}
return points;
}
private static double cubicBezier(double p0, double p1, double p2, double p3, double t) {
double mt = 1 - t;
return p0 * mt * mt * mt + 3 * p1 * mt * mt * t + 3 * p2 * mt * t * t + p3 * t * t * t;
}
private static double quadBezier(double p0, double p1, double p2, double t) {
double mt = 1 - t;
return p0 * mt * mt + 2 * p1 * mt * t + p2 * t * t;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy