/* * Copyright (C) 2013-2015 F(X)yz, * Sean Phillips, Jason Pollastrini and Jose Pereda * All rights reserved. * * 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 org.fxyz.shapes.primitives.helper; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; import java.util.stream.IntStream; import javafx.beans.property.DoubleProperty; import javafx.beans.property.SimpleDoubleProperty; import javafx.geometry.Point2D; import javafx.scene.paint.Color; 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 org.fxyz.geometry.Point3D; /** * * @author José Pereda */ public class Text3DHelper { private final static int POINTS_CURVE = 10; private final String text; private List<Point3D> list; private Point3D p0; private final List<LineSegment> polis=new ArrayList<>(); public Text3DHelper(String text, String font, int size){ this.text=text; list=new ArrayList<>(); Text textNode = new Text(text); textNode.setFont(new Font(font,size)); // Convert Text to Path Path subtract = (Path)(Shape.subtract(textNode, new Rectangle(0, 0))); // Convert Path elements into lists of points defining the perimeter (exterior or interior) subtract.getElements().forEach(this::getPoints); // Group exterior polygons with their interior polygons polis.stream().filter(LineSegment::isHole).forEach(hole->{ polis.stream().filter(poly->!poly.isHole()) .filter(poly->!((Path)Shape.intersect(poly.getPath(), hole.getPath())).getElements().isEmpty()) .filter(poly->poly.getPath().contains(new Point2D(hole.getOrigen().x,hole.getOrigen().y))) .forEach(poly->poly.addHole(hole)); }); polis.removeIf(LineSegment::isHole); } public List<LineSegment> getLineSegment() { return polis; } public List<Point3D> getOffset(){ return polis.stream().sorted((p1,p2)->(int)(p1.getOrigen().x-p2.getOrigen().x)) .map(LineSegment::getOrigen).collect(Collectors.toList()); } private void getPoints(PathElement elem){ if(elem instanceof MoveTo){ list=new ArrayList<>(); p0=new Point3D((float)((MoveTo)elem).getX(),(float)((MoveTo)elem).getY(),0f); list.add(p0); } else if(elem instanceof LineTo){ list.add(new Point3D((float)((LineTo)elem).getX(),(float)((LineTo)elem).getY(),0f)); } else if(elem instanceof CubicCurveTo){ Point3D ini = (list.size()>0?list.get(list.size()-1):p0); IntStream.rangeClosed(1, POINTS_CURVE).forEach(i->list.add(evalCubicBezier((CubicCurveTo)elem, ini, ((double)i)/POINTS_CURVE))); } else if(elem instanceof QuadCurveTo){ Point3D ini = (list.size()>0?list.get(list.size()-1):p0); IntStream.rangeClosed(1, POINTS_CURVE).forEach(i->list.add(evalQuadBezier((QuadCurveTo)elem, ini, ((double)i)/POINTS_CURVE))); } else if(elem instanceof ClosePath){ list.add(p0); // Every closed path is a polygon (exterior or interior==hole) // the text, the list of points and a new path between them are // stored in a LineSegment: a continuous line that can change direction if(Math.abs(getArea())>0.001){ LineSegment line = new LineSegment(text); line.setHole(isHole()); line.setPoints(list); line.setPath(generatePath()); line.setOrigen(p0); polis.add(line); } } } private Point3D evalCubicBezier(CubicCurveTo c, Point3D ini, double t){ Point3D p=new Point3D((float)(Math.pow(1-t,3)*ini.x+ 3*t*Math.pow(1-t,2)*c.getControlX1()+ 3*(1-t)*t*t*c.getControlX2()+ Math.pow(t, 3)*c.getX()), (float)(Math.pow(1-t,3)*ini.y+ 3*t*Math.pow(1-t, 2)*c.getControlY1()+ 3*(1-t)*t*t*c.getControlY2()+ Math.pow(t, 3)*c.getY()), 0f); return p; } private Point3D evalQuadBezier(QuadCurveTo c, Point3D ini, double t){ Point3D p=new Point3D((float)(Math.pow(1-t,2)*ini.x+ 2*(1-t)*t*c.getControlX()+ Math.pow(t, 2)*c.getX()), (float)(Math.pow(1-t,2)*ini.y+ 2*(1-t)*t*c.getControlY()+ Math.pow(t, 2)*c.getY()), 0f); return p; } private double getArea(){ DoubleProperty res=new SimpleDoubleProperty(); IntStream.range(0, list.size()-1) .forEach(i->res.set(res.get()+list.get(i).crossProduct(list.get(i+1)).z)); // System.out.println("path: "+res.doubleValue()/2); return res.doubleValue()/2d; } private boolean isHole(){ // area>0 -> the path is a hole, clockwise (y up) // area<0 -> the path is a polygon, counterclockwise (y up) return getArea()>0; } private Path generatePath(){ Path path = new Path(new MoveTo(list.get(0).x,list.get(0).y)); list.stream().skip(1).forEach(p->path.getElements().add(new LineTo(p.x,p.y))); path.getElements().add(new ClosePath()); path.setStroke(Color.GREEN); // Path must be filled to allow Shape.intersect path.setFill(Color.RED); return path; } }