/* * 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; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; import javafx.beans.property.BooleanProperty; import javafx.beans.property.DoubleProperty; import javafx.beans.property.IntegerProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleDoubleProperty; import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.geometry.Bounds; import javafx.scene.DepthTest; import javafx.scene.Group; import javafx.scene.paint.Color; import javafx.scene.shape.CullFace; import javafx.scene.shape.DrawMode; import javafx.scene.shape.Mesh; import javafx.scene.shape.Path; import javafx.scene.shape.Shape; import javafx.scene.shape.TriangleMesh; import javafx.scene.transform.Translate; import org.fxyz.geometry.Point3D; import org.fxyz.shapes.primitives.helper.LineSegment; import org.fxyz.shapes.primitives.helper.MeshHelper; import org.fxyz.shapes.primitives.helper.Text3DHelper; import org.fxyz.shapes.primitives.helper.TextureMode; import org.fxyz.utils.Palette.ColorPalette; import org.fxyz.utils.Patterns; /** * * @author José Pereda */ public class Text3DMesh extends Group implements TextureMode { private final static String DEFAULT_TEXT3D = "F(X)yz 3D"; private final static String DEFAULT_FONT = "Arial"; private final static int DEFAULT_FONT_SIZE = 100; private final static double DEFAULT_HEIGHT = 10d; private final static double DEFAULT_GAP = 0d; private final static int DEFAULT_LEVEL = 1; private final static boolean DEFAULT_JOIN_SEGMENTS = true; private final static char SPACE = 32; private ObservableList<TexturedMesh> meshes=null; private List<Point3D> offset; public Text3DMesh(){ this(DEFAULT_TEXT3D,DEFAULT_FONT,DEFAULT_FONT_SIZE,DEFAULT_JOIN_SEGMENTS,DEFAULT_HEIGHT,DEFAULT_GAP,DEFAULT_LEVEL); } public Text3DMesh(String text3D){ this(text3D,DEFAULT_FONT,DEFAULT_FONT_SIZE,DEFAULT_JOIN_SEGMENTS,DEFAULT_HEIGHT,DEFAULT_GAP,DEFAULT_LEVEL); } public Text3DMesh(String text3D, String font,int fontSize){ this(text3D,font,fontSize,DEFAULT_JOIN_SEGMENTS,DEFAULT_HEIGHT,DEFAULT_GAP,DEFAULT_LEVEL); } public Text3DMesh(String text3D, String font,int fontSize, boolean joinSegments){ this(text3D,font,fontSize,joinSegments,DEFAULT_HEIGHT,DEFAULT_GAP,DEFAULT_LEVEL); } public Text3DMesh(String text3D, double height){ this(text3D,DEFAULT_FONT,DEFAULT_FONT_SIZE,DEFAULT_JOIN_SEGMENTS,height,DEFAULT_GAP, DEFAULT_LEVEL); } public Text3DMesh(String text3D, double height, double gap){ this(text3D,DEFAULT_FONT,DEFAULT_FONT_SIZE,DEFAULT_JOIN_SEGMENTS,height,gap, DEFAULT_LEVEL); } public Text3DMesh(String text3D, double height, int level){ this(text3D,DEFAULT_FONT,DEFAULT_FONT_SIZE,DEFAULT_JOIN_SEGMENTS,height,DEFAULT_GAP,level); } public Text3DMesh(String text3D, double height, double gap, int level){ this(text3D,DEFAULT_FONT,DEFAULT_FONT_SIZE,DEFAULT_JOIN_SEGMENTS,height,gap,level); } public Text3DMesh(String text3D, String font, int fontSize, boolean joinSegments, double height, double gap, int level){ setText3D(text3D); setFont(font); setFontSize(fontSize); setJoinSegments(joinSegments); setHeight(height); setGap(gap); setLevel(level); updateMesh(); } private final StringProperty text3D = new SimpleStringProperty(DEFAULT_TEXT3D){ @Override protected void invalidated() { if(meshes!=null){ updateMesh(); } } }; public String getText3D() { return text3D.get(); } public final void setText3D(String value) { text3D.set(value); } public StringProperty text3DProperty() { return text3D; } private final StringProperty font = new SimpleStringProperty(DEFAULT_FONT){ @Override protected void invalidated() { if(meshes!=null){ updateMesh(); } } }; public String getFont() { return font.get(); } public final void setFont(String value) { font.set(value); } public StringProperty fontProperty() { return font; } private final IntegerProperty fontSize = new SimpleIntegerProperty(DEFAULT_FONT_SIZE){ @Override protected void invalidated() { if(meshes!=null){ updateMesh(); } } }; public int getFontSize() { return fontSize.get(); } public final void setFontSize(int value) { fontSize.set(value); } public IntegerProperty fontSizeProperty() { return fontSize; } private final DoubleProperty height = new SimpleDoubleProperty(DEFAULT_HEIGHT){ @Override protected void invalidated() { if(meshes!=null){ updateMesh(); } } }; public double getHeight() { return height.get(); } public final void setHeight(double value) { height.set(value); } public DoubleProperty heightProperty() { return height; } private final DoubleProperty gap = new SimpleDoubleProperty(DEFAULT_GAP){ @Override protected void invalidated() { if(meshes!=null){ updateMesh(); } } }; public double getGap() { return gap.get(); } public final void setGap(double value) { gap.set(value); } public DoubleProperty gapProperty() { return gap; } private final IntegerProperty level = new SimpleIntegerProperty(DEFAULT_LEVEL){ @Override protected void invalidated() { if(meshes!=null){ updateMesh(); } } }; public final int getLevel() { return level.get(); } public final void setLevel(int value) { level.set(value); } public final IntegerProperty levelProperty() { return level; } private final BooleanProperty joinSegments = new SimpleBooleanProperty(DEFAULT_JOIN_SEGMENTS){ @Override protected void invalidated() { if(meshes!=null){ updateMesh(); } } }; public boolean isJoinSegments() { return joinSegments.get(); } public final void setJoinSegments(boolean value) { joinSegments.set(value); } public BooleanProperty joinSegmentsProperty() { return joinSegments; } protected final void updateMesh() { // 1. Full Text to get position of each letter Text3DHelper helper = new Text3DHelper(text3D.get(), font.get(), fontSize.get()); offset=helper.getOffset(); // 2. Create mesh for each LineSegment meshes=FXCollections.<TexturedMesh>observableArrayList(); indLetters=new AtomicInteger(); indSegments=new AtomicInteger(); letterPath=new Path(); text3D.get().chars().mapToObj(i->(char)i).filter(c->c!=SPACE) .forEach(letter->createLetter(letter.toString())); getChildren().setAll(meshes); updateTransforms(); } private AtomicInteger indSegments, indLetters; private Shape letterPath=new Path(); private void createLetter(String letter) { Text3DHelper helper = new Text3DHelper(letter, font.get(), fontSize.get()); List<Point3D> origin = helper.getOffset(); final int ind=indSegments.get(); helper.getLineSegment().stream().map(poly->poly.getPath()).forEach(path->letterPath=Shape.union(letterPath, path)); helper.getLineSegment().stream().forEach(poly->{ final List<Point3D> points=poly.getPoints(); List<List<Point3D>> holes=null; if(poly.getHoles().size()>0){ holes=poly.getHoles().stream().map(LineSegment::getPoints).collect(Collectors.toList()); } List<Point3D> invert = IntStream.range(0,points.size()) .mapToObj(i->points.get(points.size()-1-i)) .distinct().collect(Collectors.toList()); Bounds bounds = null; if(joinSegments.get()){ bounds=letterPath.getBoundsInParent(); } TriangulatedMesh polyMesh = new TriangulatedMesh(invert,holes,level.get(),height.get(),0d,bounds); if(indSegments.get()>ind && joinSegments.get()){ /* Combine new polyMesh with previous polyMesh into one single polyMesh */ MeshHelper mh = new MeshHelper((TriangleMesh)meshes.get(meshes.size()-1).getMesh()); MeshHelper mh1 = new MeshHelper((TriangleMesh)polyMesh.getMesh()); mh1.addMesh(mh); polyMesh.updateMesh(mh1); meshes.set(meshes.size()-1,polyMesh); } else { meshes.add(polyMesh); } polyMesh.getTransforms().addAll(new Translate(offset.get(ind).x-origin.get(0).x+indLetters.get()*gap.doubleValue(),0,0)); polyMesh.setCullFace(CullFace.BACK); polyMesh.setDrawMode(DrawMode.FILL); polyMesh.setDepthTest(DepthTest.ENABLE); polyMesh.setId(poly.getLetter()); System.out.println("l "+poly.getLetter()); indSegments.getAndIncrement(); }); indLetters.getAndIncrement(); } @Override public void setTextureModeNone() { meshes.stream().forEach(m->m.setTextureModeNone()); } @Override public void setTextureModeNone(Color color) { meshes.stream().forEach(m->m.setTextureModeNone(color)); } @Override public void setTextureModeNone(Color color, String image) { meshes.stream().forEach(m->m.setTextureModeNone(color,image)); } @Override public void setTextureModeImage(String image) { meshes.stream().forEach(m->m.setTextureModeImage(image)); } @Override public void setTextureModePattern(Patterns.CarbonPatterns pattern, double scale) { meshes.stream().forEach(m->m.setTextureModePattern(pattern, scale)); } @Override public void setTextureModeVertices3D(int colors, Function<Point3D, Number> dens) { meshes.stream().forEach(m->m.setTextureModeVertices3D(colors, dens)); } @Override public void setTextureModeVertices3D(ColorPalette palette, int colors, Function<Point3D, Number> dens) { meshes.stream().forEach(m->m.setTextureModeVertices3D(palette, colors, dens)); } @Override public void setTextureModeVertices3D(int colors, Function<Point3D, Number> dens, double min, double max) { meshes.stream().forEach(m->m.setTextureModeVertices3D(colors, dens, min, max)); } @Override public void setTextureModeVertices1D(int colors, Function<Number, Number> function) { meshes.stream().forEach(m->m.setTextureModeVertices1D(colors, function)); } @Override public void setTextureModeVertices1D(ColorPalette palette, int colors, Function<Number, Number> function) { meshes.stream().forEach(m->m.setTextureModeVertices1D(palette, colors, function)); } @Override public void setTextureModeVertices1D(int colors, Function<Number, Number> function, double min, double max) { meshes.stream().forEach(m->m.setTextureModeVertices1D(colors, function, min, max)); } @Override public void setTextureModeFaces(int colors) { meshes.stream().forEach(m->m.setTextureModeFaces(colors)); } @Override public void setTextureModeFaces(ColorPalette palette, int colors) { meshes.stream().forEach(m->m.setTextureModeFaces(palette, colors)); } @Override public void updateF(List<Number> values) { meshes.stream().forEach(m->m.updateF(values)); } public void setDrawMode(DrawMode mode) { meshes.stream().forEach(m->m.setDrawMode(mode)); } private void updateTransforms() { meshes.stream().forEach(m->m.updateTransforms()); } public TexturedMesh getMeshFromLetter(String letter){ return meshes.stream().filter(p->p.getId().equals(letter)).findFirst().orElse(meshes.get(0)); } public TexturedMesh getMeshFromLetter(String letter, int order){ return meshes.stream().filter(p->p.getId().equals(letter)).skip(order-1).findFirst().orElse(meshes.get(0)); } }