/*
* 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.Arrays;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.geometry.Bounds;
import javafx.scene.paint.Color;
import javafx.scene.shape.MeshView;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.TriangleMesh;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Scale;
import javafx.scene.transform.Translate;
import org.fxyz.geometry.Face3;
import org.fxyz.geometry.Point3D;
import org.fxyz.shapes.primitives.helper.MeshHelper;
import org.fxyz.shapes.primitives.helper.TextureMode;
import org.fxyz.shapes.primitives.helper.TriangleMeshHelper;
import static org.fxyz.shapes.primitives.helper.TriangleMeshHelper.DEFAULT_COLORS;
import static org.fxyz.shapes.primitives.helper.TriangleMeshHelper.DEFAULT_COLOR_PALETTE;
import static org.fxyz.shapes.primitives.helper.TriangleMeshHelper.DEFAULT_DENSITY_FUNCTION;
import static org.fxyz.shapes.primitives.helper.TriangleMeshHelper.DEFAULT_DIFFUSE_COLOR;
import static org.fxyz.shapes.primitives.helper.TriangleMeshHelper.DEFAULT_PATTERN;
import static org.fxyz.shapes.primitives.helper.TriangleMeshHelper.DEFAULT_PATTERN_SCALE;
import static org.fxyz.shapes.primitives.helper.TriangleMeshHelper.DEFAULT_UNIDIM_FUNCTION;
import org.fxyz.shapes.primitives.helper.TriangleMeshHelper.SectionType;
import org.fxyz.shapes.primitives.helper.TriangleMeshHelper.TextureType;
import org.fxyz.utils.Palette.ColorPalette;
import org.fxyz.utils.Patterns.CarbonPatterns;
/**
* TexturedMesh is a base class that provides support for different mesh implementations
* taking into account four different kind of textures
* - None
* - Image
* - Colored vertices
* - Colored faces
*
* For the last two ones, number of colors and density map have to be provided
*
* Any subclass must use mesh, listVertices and listFaces
*
* @author jpereda
*/
public abstract class TexturedMesh extends MeshView implements TextureMode {
private TriangleMeshHelper helper = new TriangleMeshHelper();
protected TriangleMesh mesh;
protected List<Point3D> listVertices = new ArrayList<>();
protected final List<Face3> listTextures = new ArrayList<>();
protected final List<Face3> listFaces = new ArrayList<>();
protected float[] textureCoords;
protected int[] smoothingGroups;
protected final Rectangle rectMesh=new Rectangle(0,0);
protected final Rectangle areaMesh=new Rectangle(0,0);
protected Rotate rotateX, rotateY, rotateZ;
protected Translate translate;
protected Scale scale;
protected TexturedMesh(){
setMaterial(helper.getMaterial());
}
private final ObjectProperty<SectionType> sectionType = new SimpleObjectProperty<SectionType>(SectionType.CIRCLE){
@Override
protected void invalidated() {
if(mesh!=null){
updateMesh();
}
}
};
public SectionType getSectionType() {
return sectionType.get();
}
public void setSectionType(SectionType value) {
sectionType.set(value);
}
public ObjectProperty sectionTypeProperty() {
return sectionType;
}
private final ObjectProperty<TextureType> textureType = new SimpleObjectProperty<TextureType>(TextureType.NONE){
@Override
protected void invalidated() {
if(mesh!=null){
updateTexture();
updateTextureOnFaces();
}
}
};
@Override
public void setTextureModeNone() {
setTextureModeNone(Color.WHITE);
}
@Override
public void setTextureModeNone(Color color) {
if(color!=null){
helper.setTextureType(TextureType.NONE);
helper.getMaterialWithColor(color);
}
setTextureType(helper.getTextureType());
}
@Override
public void setTextureModeNone(Color color, String image) {
if(color!=null){
helper.setTextureType(TextureType.NONE);
setMaterial(helper.getMaterialWithColor(color, image));
}
setTextureType(helper.getTextureType());
}
@Override
public void setTextureModeImage(String image) {
if(image!=null && !image.isEmpty()){
helper.setTextureType(TextureType.IMAGE);
helper.getMaterialWithImage(image);
setTextureType(helper.getTextureType());
}
}
@Override
public void setTextureModePattern(CarbonPatterns pattern, double scale) {
helper.setTextureType(TextureType.PATTERN);
patternScale.set(scale);
carbonPatterns.set(pattern);
helper.getMaterialWithPattern(pattern);
setTextureType(helper.getTextureType());
}
@Override
public void setTextureModeVertices3D(int colors, Function<Point3D, Number> dens) {
helper.setTextureType(TextureType.COLORED_VERTICES_3D);
setColors(colors);
createPalette(getColors());
setDensity(dens);
helper.setDensity(dens);
setTextureType(helper.getTextureType());
}
@Override
public void setTextureModeVertices3D(ColorPalette palette, int colors, Function<Point3D, Number> dens) {
helper.setTextureType(TextureType.COLORED_VERTICES_3D);
setColors(colors);
setColorPalette(palette);
createPalette(getColors());
setDensity(dens);
helper.setDensity(dens);
setTextureType(helper.getTextureType());
}
@Override
public void setTextureModeVertices3D(int colors, Function<Point3D, Number> dens, double min, double max) {
helper.setTextureType(TextureType.COLORED_VERTICES_3D);
setMinGlobal(min);
setMaxGlobal(max);
setColors(colors);
createPalette(getColors());
setDensity(dens);
helper.setDensity(dens);
setTextureType(helper.getTextureType());
}
@Override
public void setTextureModeVertices1D(int colors, Function<Number, Number> function) {
helper.setTextureType(TextureType.COLORED_VERTICES_1D);
setColors(colors);
createPalette(getColors());
setFunction(function);
helper.setFunction(function);
setTextureType(helper.getTextureType());
}
@Override
public void setTextureModeVertices1D(ColorPalette palette, int colors, Function<Number, Number> function) {
helper.setTextureType(TextureType.COLORED_VERTICES_1D);
setColors(colors);
setColorPalette(palette);
createPalette(getColors());
setFunction(function);
helper.setFunction(function);
setTextureType(helper.getTextureType());
}
@Override
public void setTextureModeVertices1D(int colors, Function<Number, Number> function, double min, double max) {
helper.setTextureType(TextureType.COLORED_VERTICES_1D);
setMinGlobal(min);
setMaxGlobal(max);
setColors(colors);
createPalette(getColors());
setFunction(function);
helper.setFunction(function);
setTextureType(helper.getTextureType());
}
@Override
public void setTextureModeFaces(int colors) {
helper.setTextureType(TextureType.COLORED_FACES);
setColors(colors);
createPalette(getColors());
setTextureType(helper.getTextureType());
}
@Override
public void setTextureModeFaces(ColorPalette palette, int colors) {
helper.setTextureType(TextureType.COLORED_FACES);
setColors(colors);
setColorPalette(palette);
createPalette(getColors());
setTextureType(helper.getTextureType());
}
@Override
public void updateF(List<Number> values) {
listVertices=IntStream.range(0,values.size()).mapToObj(i->{
Point3D p=listVertices.get(i);
p.f=values.get(i).floatValue();
return p;
}).collect(Collectors.toList());
updateTextureOnFaces();
}
public TextureType getTextureType() {
return textureType.get();
}
public void setTextureType(TextureType value) {
textureType.set(value);
}
public ObjectProperty textureTypeProperty() {
return textureType;
}
private final DoubleProperty patternScale = new SimpleDoubleProperty(DEFAULT_PATTERN_SCALE){
@Override
protected void invalidated() {
updateTexture();
}
};
public final double getPatternScale(){
return patternScale.get();
}
public final void setPatternScale(double scale){
patternScale.set(scale);
}
public DoubleProperty patternScaleProperty(){
return patternScale;
}
private final IntegerProperty colors = new SimpleIntegerProperty(DEFAULT_COLORS){
@Override protected void invalidated() {
createPalette(getColors());
}
};
public final int getColors() {
return colors.get();
}
public final void setColors(int value) {
colors.set(value);
}
public IntegerProperty colorsProperty() {
return colors;
}
private final ObjectProperty<ColorPalette> colorPalette = new SimpleObjectProperty<ColorPalette>(DEFAULT_COLOR_PALETTE){
@Override protected void invalidated() {
createPalette(getColors());
updateTexture();
updateTextureOnFaces();
}
};
public ColorPalette getColorPalette() {
return colorPalette.get();
}
public final void setColorPalette(ColorPalette value) {
colorPalette.set(value);
}
public ObjectProperty colorPaletteProperty() {
return colorPalette;
}
private final ObjectProperty<Color> diffuseColor = new SimpleObjectProperty<Color>(DEFAULT_DIFFUSE_COLOR){
@Override protected void invalidated() {
updateMaterial();
}
};
private final ObjectProperty<CarbonPatterns> carbonPatterns = new SimpleObjectProperty<CarbonPatterns>(DEFAULT_PATTERN){
@Override
protected void invalidated() {
helper.getMaterialWithPattern(get());
}
};
public final CarbonPatterns getCarbonPattern(){
return carbonPatterns.get();
}
public final void setCarbonPattern(CarbonPatterns cp){
carbonPatterns.set(cp);
}
public ObjectProperty<CarbonPatterns> getCarbonPatterns() {
return carbonPatterns;
}
public Color getDiffuseColor() {
return diffuseColor.get();
}
public void setDiffuseColor(Color value) {
diffuseColor.set(value);
}
public ObjectProperty diffuseColorProperty() {
return diffuseColor;
}
private final ObjectProperty<Function<Point3D, Number>> density = new SimpleObjectProperty<Function<Point3D, Number>>(DEFAULT_DENSITY_FUNCTION){
@Override protected void invalidated() {
helper.setDensity(density.get());
updateTextureOnFaces();
}
};
public final Function<Point3D, Number> getDensity(){
return density.get();
}
public final void setDensity(Function<Point3D, Number> value){
this.density.set(value);
}
public final ObjectProperty<Function<Point3D, Number>> densityProperty() {
return density;
}
private final ObjectProperty<Function<Number, Number>> function = new SimpleObjectProperty<Function<Number, Number>>(DEFAULT_UNIDIM_FUNCTION){
@Override protected void invalidated() {
helper.setFunction(function.get());
updateTextureOnFaces();
}
};
public Function<Number, Number> getFunction() {
return function.get();
}
public void setFunction(Function<Number, Number> value) {
function.set(value);
}
public ObjectProperty functionProperty() {
return function;
}
private final DoubleProperty minGlobal = new SimpleDoubleProperty();
public double getMinGlobal() {
return minGlobal.get();
}
public void setMinGlobal(double value) {
minGlobal.set(value);
}
public DoubleProperty minGlobalProperty() {
return minGlobal;
}
private final DoubleProperty maxGlobal = new SimpleDoubleProperty();
public double getMaxGlobal() {
return maxGlobal.get();
}
public void setMaxGlobal(double value) {
maxGlobal.set(value);
}
public DoubleProperty maxGlobalProperty() {
return maxGlobal;
}
private void createPalette(int colors) {
helper.createPalette(colors, false, colorPalette.get());
helper.getMaterialWithPalette();
}
public void updateMaterial(){
helper.getMaterialWithColor(diffuseColor.get());
}
public void updateVertices(float factor){
if(mesh!=null){
mesh.getPoints().setAll(helper.updateVertices(listVertices, factor));
}
}
private void updateTexture(){
if(mesh!=null){
switch(textureType.get()){
case NONE:
mesh.getTexCoords().setAll(0f,0f);
break;
case IMAGE:
mesh.getTexCoords().setAll(textureCoords);
break;
case PATTERN:
if(areaMesh.getHeight()>0 && areaMesh.getWidth()>0){
mesh.getTexCoords().setAll(
helper.updateTexCoordsWithPattern((int)rectMesh.getWidth(),
(int)rectMesh.getHeight(),patternScale.get(),
areaMesh.getHeight()/areaMesh.getWidth()));
} else {
mesh.getTexCoords().setAll(
helper.updateTexCoordsWithPattern((int)rectMesh.getWidth(),
(int)rectMesh.getHeight(),patternScale.get()));
}
break;
case COLORED_VERTICES_1D:
mesh.getTexCoords().setAll(helper.getTexturePaletteArray());
break;
case COLORED_VERTICES_3D:
mesh.getTexCoords().setAll(helper.getTexturePaletteArray());
break;
case COLORED_FACES:
mesh.getTexCoords().setAll(helper.getTexturePaletteArray());
break;
}
}
}
private void updateTextureOnFaces(){
// textures for level
if(mesh!=null){
switch(textureType.get()){
case NONE:
mesh.getFaces().setAll(helper.updateFacesWithoutTexture(listFaces));
break;
case IMAGE:
if(listTextures.size()>0){
mesh.getFaces().setAll(helper.updateFacesWithTextures(listFaces,listTextures));
} else {
mesh.getFaces().setAll(helper.updateFacesWithVertices(listFaces));
}
break;
case PATTERN:
mesh.getFaces().setAll(helper.updateFacesWithTextures(listFaces,listTextures));
break;
case COLORED_VERTICES_1D:
if(minGlobal.get()<maxGlobal.get()){
mesh.getFaces().setAll(helper.updateFacesWithFunctionMap(listVertices, listFaces, minGlobal.get(),maxGlobal.get()));
} else {
// int[] f = helper.updateFacesWithFunctionMap(listVertices, listFaces);
// for(int i=0; i<f.length/6; i+=6){
// System.out.println("i "+f[i+1]+" "+f[i+3]+" "+f[i+5]);
// }
mesh.getFaces().setAll(helper.updateFacesWithFunctionMap(listVertices, listFaces));
}
break;
case COLORED_VERTICES_3D:
if(minGlobal.get()<maxGlobal.get()){
mesh.getFaces().setAll(helper.updateFacesWithDensityMap(listVertices, listFaces, minGlobal.get(),maxGlobal.get()));
} else {
mesh.getFaces().setAll(helper.updateFacesWithDensityMap(listVertices, listFaces));
}
break;
case COLORED_FACES:
mesh.getFaces().setAll(helper.updateFacesWithFaces(listFaces));
break;
}
}
}
protected abstract void updateMesh();
/*
This method allows replacing the original mesh with one given by a TriangleMesh
being set with MeshHelper.
This allows combining several meshes into one, creating one single node.
*/
protected void updateMesh(MeshHelper meshHelper){
setMesh(null);
mesh=createMesh(meshHelper);
setMesh(mesh);
}
protected void createTexCoords(int width, int height){
rectMesh.setWidth(width);
rectMesh.setHeight(height);
textureCoords=helper.createTexCoords(width, height);
}
protected void createReverseTexCoords(int width, int height){
rectMesh.setWidth(width);
rectMesh.setHeight(height);
textureCoords=helper.createReverseTexCoords(width, height);
}
protected MeshHelper precreateMesh() {
MeshHelper mh = new MeshHelper();
mh.setPoints(helper.updateVertices(listVertices));
switch (textureType.get()) {
case NONE:
mh.setTexCoords(textureCoords);
mh.setFaces(helper.updateFacesWithTextures(listFaces, listTextures));
break;
case PATTERN:
if (areaMesh.getHeight() > 0 && areaMesh.getWidth() > 0) {
mh.setTexCoords(
helper.updateTexCoordsWithPattern((int) rectMesh.getWidth(),
(int) rectMesh.getHeight(), patternScale.get(),
areaMesh.getHeight() / areaMesh.getWidth()));
} else {
mh.setTexCoords(
helper.updateTexCoordsWithPattern((int) rectMesh.getWidth(),
(int) rectMesh.getHeight(), patternScale.get()));
}
mh.setFaces(helper.updateFacesWithTextures(listFaces, listTextures));
break;
case IMAGE:
mh.setTexCoords(textureCoords);
if (listTextures.size() > 0) {
mh.setFaces(helper.updateFacesWithTextures(listFaces, listTextures));
} else {
mh.setFaces(helper.updateFacesWithVertices(listFaces));
}
break;
case COLORED_VERTICES_1D:
mh.setTexCoords(helper.getTexturePaletteArray());
mh.setFaces(helper.updateFacesWithFunctionMap(listVertices, listFaces));
break;
case COLORED_VERTICES_3D:
mh.setTexCoords(helper.getTexturePaletteArray());
mh.setFaces(helper.updateFacesWithDensityMap(listVertices, listFaces));
break;
case COLORED_FACES:
mh.setTexCoords(helper.getTexturePaletteArray());
mh.setFaces(helper.updateFacesWithFaces(listFaces));
break;
}
int[] faceSmoothingGroups = new int[listFaces.size()]; // 0 == hard edges
Arrays.fill(faceSmoothingGroups, 1); // 1: soft edges, all the faces in same surface
if (smoothingGroups != null) {
// for(int i=0; i<smoothingGroups.length; i++){
// System.out.println("i: "+smoothingGroups[i]);
// }
mh.setFaceSmoothingGroups(smoothingGroups);
} else {
mh.setFaceSmoothingGroups(faceSmoothingGroups);
}
return mh;
}
protected TriangleMesh createMesh(MeshHelper mh) {
float[] points0=mh.getPoints();
float[] f = mh.getF();
listVertices.clear();
listVertices.addAll(IntStream.range(0, points0.length/3)
.mapToObj(i -> new Point3D(points0[3*i], points0[3*i+1], points0[3*i+2],f[i]))
.collect(Collectors.toList()));
textureCoords=mh.getTexCoords();
int[] faces0 = mh.getFaces();
listFaces.clear();
listFaces.addAll(IntStream.range(0, faces0.length/6)
.mapToObj(i -> new Face3(faces0[6*i+0], faces0[6*i+2], faces0[6*i+4]))
.collect(Collectors.toList()));
listTextures.clear();
// listTextures.addAll(listFaces);
listTextures.addAll(IntStream.range(0, faces0.length/6)
.mapToObj(i -> new Face3(faces0[6*i+1], faces0[6*i+3], faces0[6*i+5]))
.collect(Collectors.toList()));
smoothingGroups=mh.getFaceSmoothingGroups();
return createMesh();
}
protected TriangleMesh createMesh(){
TriangleMesh triangleMesh = new TriangleMesh();
triangleMesh.getPoints().setAll(helper.updateVertices(listVertices));
switch(textureType.get()){
case NONE:
triangleMesh.getTexCoords().setAll(textureCoords);
triangleMesh.getFaces().setAll(helper.updateFacesWithTextures(listFaces,listTextures));
break;
case PATTERN:
if(areaMesh.getHeight()>0 && areaMesh.getWidth()>0){
triangleMesh.getTexCoords().setAll(
helper.updateTexCoordsWithPattern((int)rectMesh.getWidth(),
(int)rectMesh.getHeight(),patternScale.get(),
areaMesh.getHeight()/areaMesh.getWidth()));
} else {
triangleMesh.getTexCoords().setAll(
helper.updateTexCoordsWithPattern((int)rectMesh.getWidth(),
(int)rectMesh.getHeight(),patternScale.get()));
}
triangleMesh.getFaces().setAll(helper.updateFacesWithTextures(listFaces,listTextures));
break;
case IMAGE:
triangleMesh.getTexCoords().setAll(textureCoords);
if(listTextures.size()>0){
triangleMesh.getFaces().setAll(helper.updateFacesWithTextures(listFaces,listTextures));
} else {
triangleMesh.getFaces().setAll(helper.updateFacesWithVertices(listFaces));
}
break;
case COLORED_VERTICES_1D:
triangleMesh.getTexCoords().setAll(helper.getTexturePaletteArray());
triangleMesh.getFaces().setAll(helper.updateFacesWithFunctionMap(listVertices, listFaces));
break;
case COLORED_VERTICES_3D:
triangleMesh.getTexCoords().setAll(helper.getTexturePaletteArray());
triangleMesh.getFaces().setAll(helper.updateFacesWithDensityMap(listVertices, listFaces));
break;
case COLORED_FACES:
triangleMesh.getTexCoords().setAll(helper.getTexturePaletteArray());
triangleMesh.getFaces().setAll(helper.updateFacesWithFaces(listFaces));
break;
}
int[] faceSmoothingGroups = new int[listFaces.size()]; // 0 == hard edges
Arrays.fill(faceSmoothingGroups, 1); // 1: soft edges, all the faces in same surface
if(smoothingGroups!=null){
// for(int i=0; i<smoothingGroups.length; i++){
// System.out.println("i: "+smoothingGroups[i]);
// }
triangleMesh.getFaceSmoothingGroups().addAll(smoothingGroups);
} else {
triangleMesh.getFaceSmoothingGroups().addAll(faceSmoothingGroups);
}
System.out.println("nodes: "+listVertices.size()+", faces: "+listFaces.size());
// System.out.println("area: "+helper.getMeshArea(listVertices, listFaces));
return triangleMesh;
}
protected void updateTransforms(){
getTransforms().removeAll(rotateX,rotateY,rotateZ,scale);
Bounds bounds=getBoundsInLocal();
javafx.geometry.Point3D p = new javafx.geometry.Point3D(
(bounds.getMaxX()+bounds.getMinX())/2d,
(bounds.getMaxY()+bounds.getMinY())/2d,
(bounds.getMaxZ()+bounds.getMinZ())/2d);
translate = new Translate(0,0,0);
rotateX = new Rotate(0, p.getX(), p.getY(), p.getZ(), Rotate.X_AXIS);
rotateY = new Rotate(0, p.getX(), p.getY(), p.getZ(), Rotate.Y_AXIS);
rotateZ = new Rotate(0, p.getX(), p.getY(), p.getZ(), Rotate.Z_AXIS);
scale = new Scale(1,1,1, p.getX(), p.getY(), p.getZ());
getTransforms().addAll(translate,rotateZ,rotateY,rotateX,scale);
}
public Translate getTranslate() {
if(translate==null){
updateTransforms();
}
return translate;
}
public Rotate getRotateX() {
if(rotateX==null){
updateTransforms();
}
return rotateX;
}
public Rotate getRotateY() {
if(rotateY==null){
updateTransforms();
}
return rotateY;
}
public Rotate getRotateZ() {
if(rotateZ==null){
updateTransforms();
}
return rotateZ;
}
public Scale getScale() {
if(scale==null){
updateTransforms();
}
return scale;
}
protected double polygonalSection(double angle){
if(sectionType.get().equals(SectionType.CIRCLE)){
return 1d;
}
int n=sectionType.get().getSides();
return Math.cos(Math.PI/n)/Math.cos((2d*Math.atan(1d/Math.tan((n*angle)/2d)))/n);
}
protected double polygonalSize(double radius){
if(sectionType.get().equals(SectionType.CIRCLE)){
return 2d*Math.PI*radius;
}
int n=sectionType.get().getSides();
return n*Math.cos(Math.PI/n)*Math.log(-1d - 2d/(-1d + Math.sin(Math.PI/n)))*radius;
}
public Point3D getOrigin(){
if(listVertices.size()>0){
return listVertices.get(0);
}
return new Point3D(0f,0f,0f);
}
public int getIntersections(Point3D origin, Point3D direction){
setTextureModeFaces(10);
int[] faces= helper.updateFacesWithIntersections(origin, direction, listVertices, listFaces);
mesh.getFaces().setAll(faces);
long time=System.currentTimeMillis();
List<Face3> listIntersections = helper.getListIntersections(origin, direction, listVertices, listFaces);
System.out.println("t: "+(System.currentTimeMillis()-time));
listIntersections.forEach(System.out::println);
return listIntersections.size();
}
}