/*
JWildfire - an image and animation processor written in Java
Copyright (C) 1995-2017 Andreas Maschke
This is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either version 2.1 of the
License, or (at your option) any later version.
This software 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along with this software;
if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jwildfire.create.tina.variation.mesh;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jwildfire.base.Tools;
import org.jwildfire.base.mathlib.MathLib;
import org.jwildfire.base.mathlib.VecMathLib.VectorD;
public class SimpleMesh {
private List<Vertex> vertices = new ArrayList<>();
private Map<String, Integer> verticesMap = new HashMap<>();
private List<Face> faces = new ArrayList<>();
private BoundingBox boundingBox;
public class BoundingBox {
private final double xmin, xmax, xcentre;
private final double ymin, ymax, ycentre;
private final double zmin, zmax, zcentre;
public BoundingBox(double xmin, double xmax, double ymin, double ymax, double zmin, double zmax) {
super();
this.xmin = xmin;
this.xmax = xmax;
this.xcentre = this.xmin + (this.xmax - this.xmin) / 2.0;
this.ymin = ymin;
this.ymax = ymax;
this.ycentre = this.ymin + (this.ymax - this.ymin) / 2.0;
this.zmin = zmin;
this.zmax = zmax;
this.zcentre = this.zmin + (this.zmax - this.zmin) / 2.0;
}
public double getXmin() {
return xmin;
}
public double getXmax() {
return xmax;
}
public double getYmin() {
return ymin;
}
public double getYmax() {
return ymax;
}
public double getZmin() {
return zmin;
}
public double getZmax() {
return zmax;
}
public double getXcentre() {
return xcentre;
}
public double getYcentre() {
return ycentre;
}
public double getZcentre() {
return zcentre;
}
}
private String calculateVertexKey(double x, double y, double z) {
final double prec = 10000.0;
return String.valueOf(Tools.FTOI(x * prec)) + "#" + String.valueOf(Tools.FTOI(y * prec)) + "#" + String.valueOf(Tools.FTOI(z * prec));
}
public int addVertex(double x, double y, double z) {
String key = calculateVertexKey(x, y, z);
Integer idx = verticesMap.get(key);
if (idx != null) {
return idx.intValue();
}
Vertex p = new Vertex();
p.x = (float) x;
p.y = (float) y;
p.z = (float) z;
int res = vertices.size();
vertices.add(p);
verticesMap.put(key, Integer.valueOf(res));
boundingBox = null;
return res;
}
public int addVertex(double x, double y, double z, double u, double v) {
String key = calculateVertexKey(x, y, z);
Integer idx = verticesMap.get(key);
if (idx != null) {
return idx.intValue();
}
VertexWithUV p = new VertexWithUV();
p.x = (float) x;
p.y = (float) y;
p.z = (float) z;
p.u = (float) u;
p.v = (float) v;
int res = vertices.size();
vertices.add(p);
verticesMap.put(key, Integer.valueOf(res));
boundingBox = null;
return res;
}
public void addFace(int p1, int p2, int p3) {
Face f = new Face();
f.v1 = p1;
f.v2 = p2;
f.v3 = p3;
faces.add(f);
}
public void addFace(int p1, int p2, int p3, int p4) {
Face f1 = new Face();
f1.v1 = p1;
f1.v2 = p2;
f1.v3 = p3;
faces.add(f1);
Face f2 = new Face();
f2.v1 = p1;
f2.v2 = p3;
f2.v3 = p4;
faces.add(f2);
}
public int getFaceCount() {
return faces.size();
}
public Face getFace(int idx) {
return faces.get(idx);
}
public Vertex getVertex(int idx) {
return vertices.get(idx);
}
public void distributeFaces() {
System.out.println("VERTICES: " + vertices.size());
List<Double> areaLst = new ArrayList<>();
double areaMin = Double.MAX_VALUE, areaMax = 0.0;
for (Face face : faces) {
Vertex p1 = getVertex(face.v1);
Vertex p2 = getVertex(face.v2);
Vertex p3 = getVertex(face.v3);
VectorD a = new VectorD(p2.x - p1.x, p2.y - p1.y, p2.z - p1.z);
VectorD b = new VectorD(p3.x - p1.x, p3.y - p1.y, p3.z - p1.z);
double area = VectorD.cross(a, b).length();
areaLst.add(area);
if (area < areaMin) {
areaMin = area;
}
if (area > areaMax) {
areaMax = area;
}
}
if (MathLib.fabs(areaMin - areaMax) > MathLib.EPSILON) {
int maxFaces = faces.size() < 5000 ? 5000 : 500;
List<Face> newFaces = new ArrayList<>();
for (int i = 0; i < faces.size(); i++) {
Face face = faces.get(i);
int count = Math.min(Tools.FTOI(areaLst.get(i) / areaMin), maxFaces);
for (int j = 0; j < count; j++) {
newFaces.add(face);
}
}
faces = newFaces;
}
}
public void laplaceSmooth(NeightboursList neighbours, double strength) {
List<Vertex> displacements = new ArrayList<>();
// Calc displacements
for (int i = 0; i < vertices.size(); i++) {
Vertex d = new Vertex();
displacements.add(d);
List<Integer> nList = neighbours.getNeighbours(i);
if (nList.size() > 1) {
double weight = 1.0 / (double) nList.size();
Vertex v = getVertex(i);
for (Integer n : nList) {
Vertex vn = getVertex(n);
d.x += (vn.x - v.x) * weight;
d.y += (vn.y - v.y) * weight;
d.z += (vn.z - v.z) * weight;
}
}
}
// Apply displacements
for (int i = 0; i < vertices.size(); i++) {
Vertex v = getVertex(i);
Vertex d = displacements.get(i);
v.x += d.x * strength;
v.y += d.y * strength;
v.z += d.z * strength;
}
}
public void taubinSmooth(int passes, double lambda, double mu) {
NeightboursList neightbours = new NeightboursList(this);
for (int i = 0; i < passes; i++) {
laplaceSmooth(neightbours, lambda);
laplaceSmooth(neightbours, mu);
}
}
public List<Face> getFaces() {
return faces;
}
public SimpleMesh interpolate() {
SimpleMesh newMesh = new SimpleMesh();
for (Face face : getFaces()) {
Vertex v1 = getVertex(face.v1);
Vertex v2 = getVertex(face.v2);
Vertex v3 = getVertex(face.v3);
int nv1, nv2, nv3, nv4, nv5, nv6;
if (v1 instanceof VertexWithUV && v2 instanceof VertexWithUV && v3 instanceof VertexWithUV) {
VertexWithUV v4 = intersect((VertexWithUV) v1, (VertexWithUV) v2);
VertexWithUV v5 = intersect((VertexWithUV) v2, (VertexWithUV) v3);
VertexWithUV v6 = intersect((VertexWithUV) v3, (VertexWithUV) v1);
nv1 = newMesh.addVertex(v1.x, v1.y, v1.z, ((VertexWithUV) v1).u, ((VertexWithUV) v1).v);
nv2 = newMesh.addVertex(v2.x, v2.y, v2.z, ((VertexWithUV) v2).u, ((VertexWithUV) v2).v);
nv3 = newMesh.addVertex(v3.x, v3.y, v3.z, ((VertexWithUV) v3).u, ((VertexWithUV) v3).v);
nv4 = newMesh.addVertex(v4.x, v4.y, v4.z, v4.u, v4.v);
nv5 = newMesh.addVertex(v5.x, v5.y, v5.z, v5.u, v5.v);
nv6 = newMesh.addVertex(v6.x, v6.y, v6.z, v6.u, v6.v);
}
else {
Vertex v4 = intersect(v1, v2);
Vertex v5 = intersect(v2, v3);
Vertex v6 = intersect(v3, v1);
nv1 = newMesh.addVertex(v1.x, v1.y, v1.z);
nv2 = newMesh.addVertex(v2.x, v2.y, v2.z);
nv3 = newMesh.addVertex(v3.x, v3.y, v3.z);
nv4 = newMesh.addVertex(v4.x, v4.y, v4.z);
nv5 = newMesh.addVertex(v5.x, v5.y, v5.z);
nv6 = newMesh.addVertex(v6.x, v6.y, v6.z);
}
newMesh.addFace(nv1, nv4, nv6);
newMesh.addFace(nv4, nv2, nv5);
newMesh.addFace(nv5, nv3, nv6);
newMesh.addFace(nv4, nv5, nv6);
}
return newMesh;
}
private Vertex intersect(Vertex v1, Vertex v2) {
Vertex v = new Vertex();
v.x = (float) (v1.x + (v2.x - v1.x) * 0.5);
v.y = (float) (v1.y + (v2.y - v1.y) * 0.5);
v.z = (float) (v1.z + (v2.z - v1.z) * 0.5);
return v;
}
private VertexWithUV intersect(VertexWithUV v1, VertexWithUV v2) {
VertexWithUV v = new VertexWithUV();
v.x = (float) (v1.x + (v2.x - v1.x) * 0.5);
v.y = (float) (v1.y + (v2.y - v1.y) * 0.5);
v.z = (float) (v1.z + (v2.z - v1.z) * 0.5);
v.u = (float) (v1.u + (v2.u - v1.u) * 0.5);
v.v = (float) (v1.v + (v2.v - v1.v) * 0.5);
return v;
}
public BoundingBox getBoundingBox() {
if (boundingBox == null) {
double xmin = Double.MAX_VALUE, xmax = Double.MIN_VALUE;
double ymin = Double.MAX_VALUE, ymax = Double.MIN_VALUE;
double zmin = Double.MAX_VALUE, zmax = Double.MIN_VALUE;
for (int i = 0; i < vertices.size(); i++) {
Vertex v = getVertex(i);
if (v.x < xmin) {
xmin = v.x;
}
if (v.x > xmax) {
xmax = v.x;
}
if (v.y < ymin) {
ymin = v.y;
}
if (v.y > ymax) {
ymax = v.y;
}
if (v.z < zmin) {
zmin = v.z;
}
if (v.z > zmax) {
zmax = v.z;
}
}
boundingBox = new BoundingBox(xmin, xmax, ymin, ymax, zmin, zmax);
}
return boundingBox;
}
}