/*
* 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.composites;
import java.util.ArrayList;
import javafx.scene.AmbientLight;
import javafx.scene.DepthTest;
import javafx.scene.Group;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.CullFace;
import javafx.scene.shape.DrawMode;
import javafx.scene.shape.MeshView;
import javafx.scene.shape.TriangleMesh;
import org.fxyz.geometry.Point3D;
/**
*
* @author Sean
*/
public class ScatterPlotMesh extends Group {
private ArrayList<Double> xAxisData = new ArrayList<>();
private ArrayList<Double> yAxisData = new ArrayList<>();
private ArrayList<Double> zAxisData = new ArrayList<>();
public Group scatterDataGroup = new Group();
public AmbientLight selfLight = new AmbientLight(Color.WHITE);
public double nodeRadius = 1;
private double axesSize = 1000;
private boolean normalized = false;
public boolean selfLightEnabled = true;
double plotSize = 1000;
double nodeSize = 1;
public enum NodeType {SPHERE, CUBE, PYRAMID, STAR};
private NodeType defaultNodeType = NodeType.SPHERE;
public ScatterPlotMesh(boolean selfLit) {
selfLightEnabled = selfLit;
init();
}
public ScatterPlotMesh(double axesSize, double nodeRadius, boolean selfLit) {
selfLightEnabled = selfLit;
this.axesSize = axesSize;
this.nodeRadius = nodeRadius;
init();
}
private void init(){
if(selfLightEnabled) {
getChildren().add(selfLight);
}
setDepthTest(DepthTest.ENABLE);
}
public void setXYZData(ArrayList<Double> xData, ArrayList<Double> yData, ArrayList<Double> zData) {
xAxisData = xData;
yAxisData = yData;
zAxisData = zData;
getChildren().clear();
//for now we will always default to x axis
//later we could maybe dynamically determine the smallest axis and then
//uses 0's for the other axes that are larger.
ArrayList<Point3D> point3DList = new ArrayList<>();
for(int i=0;i<xAxisData.size();i++) {
//some safety checks for array sizes
double translateY = 0.0;
double translateZ = 0.0;
if(!yAxisData.isEmpty() && yAxisData.size() > i)
translateY = yAxisData.get(i);
if(!zAxisData.isEmpty() && zAxisData.size() > i)
translateZ = zAxisData.get(i);
setTranslateX(xAxisData.get(i));
//Convert to Floats and build list of adjusted points
point3DList.add(new Point3D(new Float(xAxisData.get(i)), new Float(translateY), new Float(translateZ)));
float width = 1;
final TriangleMesh mesh = new TriangleMesh();
//add each point. For each point add another point shifted on Z axis by width
//This extra point allows us to build triangles later
for(Point3D point: point3DList) {
//Rear points
//top right rear point
mesh.getPoints().addAll(point.x+width,point.y+width,point.z+width);
//top left rear point
mesh.getPoints().addAll(point.x-width,point.y+width,point.z+width);
//bottom right rear point
mesh.getPoints().addAll(point.x+width,point.y-width,point.z+width);
//bottom left rear point
mesh.getPoints().addAll(point.x-width,point.y-width,point.z+width);
//Front points
//top right front point
mesh.getPoints().addAll(point.x+width,point.y+width,point.z-width);
//top left front point
mesh.getPoints().addAll(point.x-width,point.y+width,point.z-width);
//bottom right front point
mesh.getPoints().addAll(point.x+width,point.y-width,point.z-width);
//bottom left front point
mesh.getPoints().addAll(point.x-width,point.y-width,point.z-width);
}
//add dummy Texture Coordinate
mesh.getTexCoords().addAll(0,0);
//Now generate nodes for each point
for(int p=8;p<point3DList.size()*7;p+=8) { //add each segment
//Wind the next 8 vertices as a cube. The cube itself will represent the data
//Vertices wound counter-clockwise which is the default front face of any Triangle
//Rear triangle faces should be wound clockwise to face away from center
mesh.getFaces().addAll(p,0,p+3,0,p+2,0); //TRR,BLR,BRR
mesh.getFaces().addAll(p+3,0,p,0,p+1,0); //BLR,TRR,TLR
//left side faces
mesh.getFaces().addAll(p+1,0,p+5,0,p+3,0); //TLR,TLF,BLR
mesh.getFaces().addAll(p+5,0,p+7,0,p+3,0); //TLF,BLR,BLF
//front side faces
mesh.getFaces().addAll(p+5,0,p+7,0,p+4,0); //TLF,BLF,TLR
mesh.getFaces().addAll(p+4,0,p+7,0,p+6,0); //TRF,BLF,BRF
//front side faces
mesh.getFaces().addAll(p+4,0,p+6,0,p+2,0); //TRF,BRF,BRR
mesh.getFaces().addAll(p+4,0,p+2,0,p,0); //TRF,BRR,TRR
//Top faces
mesh.getFaces().addAll(p,0,p+1,0,p+3,0); //TRR,TLR,TRF
mesh.getFaces().addAll(p+1,0,p+5,0,p+3,0); //TLR,TLF,TRF
//bottom faces
mesh.getFaces().addAll(p+3,0,p+7,0,p+6,0); //BLR,BLF,BRF
mesh.getFaces().addAll(p+3,0,p+6,0,p+2,0); //BLR,BRF,BRR
}
//Need to add the mesh to a MeshView before adding to our 3D scene
MeshView meshView = new MeshView(mesh);
meshView.setDrawMode(DrawMode.FILL); //Fill so that the line shows width
Color hsb = Color.hsb((new Double(i) / 12) * 360, 1.0, 1.0, 0.5);
PhongMaterial material = new PhongMaterial(hsb);
material.setDiffuseColor(hsb);
material.setSpecularColor(hsb);
meshView.setMaterial(material);
//Make sure you Cull the Back so that no black shows through
meshView.setCullFace(CullFace.BACK);
// //Add some ambient light so folks can see it
// Group line = new Group();
// AmbientLight light = new AmbientLight(Color.WHITE);
// light.getScope().add(meshView);
// line.getChildren().add(light);
// line.getChildren().add(meshView);
getChildren().addAll(meshView);
}
}
}