Feature: added support for creased edges in subdivision surface
modifier.
This commit is contained in:
parent
3fa56c9467
commit
592d0a0793
@ -6,7 +6,6 @@ import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import com.jme3.math.FastMath;
|
||||
import com.jme3.math.Line;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.plugins.blender.file.BlenderFileException;
|
||||
import com.jme3.scene.plugins.blender.file.Pointer;
|
||||
@ -18,8 +17,7 @@ import com.jme3.scene.plugins.blender.meshes.IndexesLoop.IndexPredicate;
|
||||
*
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
public class Edge extends Line {
|
||||
private static final long serialVersionUID = 7172714692126675311L;
|
||||
public class Edge {
|
||||
private static final Logger LOGGER = Logger.getLogger(Edge.class.getName());
|
||||
|
||||
private static final int FLAG_EDGE_NOT_IN_FACE = 0x80;
|
||||
@ -30,9 +28,8 @@ public class Edge extends Line {
|
||||
private float crease;
|
||||
/** A variable that indicates if this edge belongs to any face or not. */
|
||||
private boolean inFace;
|
||||
|
||||
public Edge() {
|
||||
}
|
||||
/** The mesh that owns the edge. */
|
||||
private TemporalMesh temporalMesh;
|
||||
|
||||
/**
|
||||
* This constructor only stores the indexes of the vertices. The position vertices should be stored
|
||||
@ -46,38 +43,17 @@ public class Edge extends Line {
|
||||
* @param inFace
|
||||
* a variable that indicates if this edge belongs to any face or not
|
||||
*/
|
||||
private Edge(int index1, int index2, float crease, boolean inFace) {
|
||||
public Edge(int index1, int index2, float crease, boolean inFace, TemporalMesh temporalMesh) {
|
||||
this.index1 = index1;
|
||||
this.index2 = index2;
|
||||
this.crease = crease;
|
||||
this.inFace = inFace;
|
||||
}
|
||||
|
||||
/**
|
||||
* This constructor stores both indexes and vertices list. The list should contain ALL verts and not
|
||||
* only those belonging to the edge.
|
||||
* @param index1
|
||||
* the first index of the edge
|
||||
* @param index2
|
||||
* the second index of the edge
|
||||
* @param crease
|
||||
* the weight of the face
|
||||
* @param inFace
|
||||
* a variable that indicates if this edge belongs to any face or not
|
||||
* @param vertices
|
||||
* the vertices of the mesh
|
||||
*/
|
||||
public Edge(int index1, int index2, float crease, boolean inFace, List<Vector3f> vertices) {
|
||||
this(index1, index2, crease, inFace);
|
||||
this.set(vertices.get(index1), vertices.get(index2));
|
||||
this.temporalMesh = temporalMesh;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Edge clone() {
|
||||
Edge result = new Edge(index1, index2, crease, inFace);
|
||||
result.setOrigin(this.getOrigin());
|
||||
result.setDirection(this.getDirection());
|
||||
return result;
|
||||
return new Edge(index1, index2, crease, inFace, temporalMesh);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -128,9 +104,8 @@ public class Edge extends Line {
|
||||
* @return the centroid of the edge
|
||||
*/
|
||||
public Vector3f computeCentroid() {
|
||||
Vector3f v1 = this.getOrigin();
|
||||
Vector3f v2 = v1.add(this.getDirection());
|
||||
return v2.addLocal(v1).divideLocal(2);
|
||||
List<Vector3f> vertices = temporalMesh.getVertices();
|
||||
return vertices.get(index1).add(vertices.get(index2)).divideLocal(2);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -159,18 +134,6 @@ public class Edge extends Line {
|
||||
index2 = temp;
|
||||
}
|
||||
|
||||
/**
|
||||
* The method sets the vertices for the first and second index.
|
||||
* @param v1
|
||||
* the first vertex
|
||||
* @param v2
|
||||
* the second vertex
|
||||
*/
|
||||
public void set(Vector3f v1, Vector3f v2) {
|
||||
this.setOrigin(v1);
|
||||
this.setDirection(v2.subtract(v1));
|
||||
}
|
||||
|
||||
/**
|
||||
* The crossing method first computes the points on both lines (that contain the edges)
|
||||
* who are closest in distance. If the distance between points is smaller than FastMath.FLT_EPSILON
|
||||
@ -198,9 +161,10 @@ public class Edge extends Line {
|
||||
* @return <b>true</b> if the edges cross and false otherwise
|
||||
*/
|
||||
public boolean cross(Edge edge) {
|
||||
Vector3f P1 = this.getOrigin(), P2 = edge.getOrigin();
|
||||
Vector3f u = this.getDirection();
|
||||
Vector3f v = edge.getDirection();
|
||||
Vector3f P1 = temporalMesh.getVertices().get(index1);
|
||||
Vector3f P2 = edge.temporalMesh.getVertices().get(edge.index1);
|
||||
Vector3f u = temporalMesh.getVertices().get(index2).subtract(P1);
|
||||
Vector3f v = edge.temporalMesh.getVertices().get(edge.index2).subtract(P2);
|
||||
float t2 = (u.x * (P2.y - P1.y) - u.y * (P2.x - P1.x)) / (u.y * v.x - u.x * v.y);
|
||||
float t1 = (P2.x - P1.x + v.x * t2) / u.x;
|
||||
Vector3f p1 = P1.add(u.mult(t1));
|
||||
@ -223,9 +187,7 @@ public class Edge extends Line {
|
||||
@Override
|
||||
public String toString() {
|
||||
String result = "Edge [" + index1 + ", " + index2 + "] {" + crease + "}";
|
||||
if (this.getOrigin() != null && this.getDirection() != null) {
|
||||
result += " -> {" + this.getOrigin() + ", " + this.getOrigin().add(this.getDirection()) + "}";
|
||||
}
|
||||
result += " (" + temporalMesh.getVertices().get(index1) + " -> " + temporalMesh.getVertices().get(index2) + ")";
|
||||
if (inFace) {
|
||||
result += "[F]";
|
||||
}
|
||||
@ -260,11 +222,13 @@ public class Edge extends Line {
|
||||
* The method loads all edges from the given mesh structure that does not belong to any face.
|
||||
* @param meshStructure
|
||||
* the mesh structure
|
||||
* @param temporalMesh
|
||||
* the owner of the edges
|
||||
* @return all edges without faces
|
||||
* @throws BlenderFileException
|
||||
* an exception is thrown when problems with file reading occur
|
||||
*/
|
||||
public static List<Edge> loadAll(Structure meshStructure) throws BlenderFileException {
|
||||
public static List<Edge> loadAll(Structure meshStructure, TemporalMesh temporalMesh) throws BlenderFileException {
|
||||
LOGGER.log(Level.FINE, "Loading all edges that do not belong to any face from mesh: {0}", meshStructure.getName());
|
||||
List<Edge> result = new ArrayList<Edge>();
|
||||
|
||||
@ -277,9 +241,10 @@ public class Edge extends Line {
|
||||
|
||||
int v1 = ((Number) edge.getFieldValue("v1")).intValue();
|
||||
int v2 = ((Number) edge.getFieldValue("v2")).intValue();
|
||||
float crease = ((Number) edge.getFieldValue("crease")).floatValue();
|
||||
// I do not know why, but blender stores (possibly only sometimes) crease as negative values and shows positive in the editor
|
||||
float crease = Math.abs(((Number) edge.getFieldValue("crease")).floatValue());
|
||||
boolean edgeInFace = (flag & Edge.FLAG_EDGE_NOT_IN_FACE) == 0;
|
||||
result.add(new Edge(v1, v2, crease, edgeInFace));
|
||||
result.add(new Edge(v1, v2, crease, edgeInFace, temporalMesh));
|
||||
}
|
||||
}
|
||||
LOGGER.log(Level.FINE, "Loaded {0} edges.", result.size());
|
||||
|
@ -355,7 +355,7 @@ public class Face implements Comparator<Integer> {
|
||||
if (i != index && i != indexToIgnore) {
|
||||
Vector3f v2 = vertices.get(i);
|
||||
float d = v2.distance(v1);
|
||||
if (d < distance && this.contains(new Edge(index, i, 0, true, vertices))) {
|
||||
if (d < distance && this.contains(new Edge(index, i, 0, true, temporalMesh))) {
|
||||
result = i;
|
||||
distance = d;
|
||||
}
|
||||
@ -378,14 +378,12 @@ public class Face implements Comparator<Integer> {
|
||||
if (!indexes.areNeighbours(index1, index2)) {
|
||||
List<Vector3f> vertices = temporalMesh.getVertices();
|
||||
|
||||
Edge e2 = new Edge();
|
||||
for (int i = 0; i < indexes.size(); ++i) {
|
||||
int i1 = this.getIndex(i);
|
||||
int i2 = this.getIndex(i + 1);
|
||||
// check if the edges have no common verts (because if they do, they cannot cross)
|
||||
if (i1 != index1 && i1 != index2 && i2 != index1 && i2 != index2) {
|
||||
e2.set(vertices.get(i1), vertices.get(i2));
|
||||
if (edge.cross(e2)) {
|
||||
if (edge.cross(new Edge(i1, i2, 0, false, temporalMesh))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -117,7 +117,7 @@ public class TemporalMesh extends Geometry {
|
||||
vertexGroups = meshHelper.loadVerticesGroups(meshStructure);
|
||||
|
||||
faces = Face.loadAll(meshStructure, userUVGroups, verticesColors, this, blenderContext);
|
||||
edges = Edge.loadAll(meshStructure);
|
||||
edges = Edge.loadAll(meshStructure, this);
|
||||
points = Point.loadAll(meshStructure);
|
||||
|
||||
this.rebuildIndexesMappings();
|
||||
@ -229,7 +229,7 @@ public class TemporalMesh extends Geometry {
|
||||
* @return <b>true</b> if the index is a boundary one and <b>false</b> otherwise
|
||||
*/
|
||||
public boolean isBoundary(Integer index) {
|
||||
List<Edge> adjacentEdges = indexToEdgeMapping.get(index);
|
||||
List<Edge> adjacentEdges = this.getAdjacentEdges(index);
|
||||
for (Edge edge : adjacentEdges) {
|
||||
if (this.isBoundary(edge)) {
|
||||
return true;
|
||||
|
@ -28,12 +28,12 @@ import com.jme3.scene.plugins.blender.textures.TexturePixel;
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
public class SubdivisionSurfaceModifier extends Modifier {
|
||||
private static final Logger LOGGER = Logger.getLogger(SubdivisionSurfaceModifier.class.getName());
|
||||
private static final Logger LOGGER = Logger.getLogger(SubdivisionSurfaceModifier.class.getName());
|
||||
|
||||
private static final int TYPE_CATMULLCLARK = 0;
|
||||
private static final int TYPE_SIMPLE = 1;
|
||||
private static final int TYPE_CATMULLCLARK = 0;
|
||||
private static final int TYPE_SIMPLE = 1;
|
||||
|
||||
private static final int FLAG_SUBDIVIDE_UVS = 0x8;
|
||||
private static final int FLAG_SUBDIVIDE_UVS = 0x8;
|
||||
|
||||
/** The subdivision type. */
|
||||
private int subdivType;
|
||||
@ -42,6 +42,8 @@ public class SubdivisionSurfaceModifier extends Modifier {
|
||||
/** Indicates if the UV's should also be subdivided. */
|
||||
private boolean subdivideUVS;
|
||||
|
||||
private List<Integer> verticesOnOriginalEdges = new ArrayList<Integer>();
|
||||
|
||||
/**
|
||||
* Constructor loads all neccessary modifier data.
|
||||
* @param modifierStructure
|
||||
@ -75,6 +77,12 @@ public class SubdivisionSurfaceModifier extends Modifier {
|
||||
TemporalMesh temporalMesh = this.getTemporalMesh(node);
|
||||
if (temporalMesh != null) {
|
||||
LOGGER.log(Level.FINE, "Applying subdivision surface modifier to: {0}", temporalMesh);
|
||||
|
||||
for (Edge edge : temporalMesh.getEdges()) {
|
||||
verticesOnOriginalEdges.add(edge.getFirstIndex());
|
||||
verticesOnOriginalEdges.add(edge.getSecondIndex());
|
||||
}
|
||||
|
||||
if (subdivType == TYPE_CATMULLCLARK) {
|
||||
for (int i = 0; i < levels; ++i) {
|
||||
this.subdivideSimple(temporalMesh);// first do simple subdivision ...
|
||||
@ -109,8 +117,21 @@ public class SubdivisionSurfaceModifier extends Modifier {
|
||||
}
|
||||
}
|
||||
|
||||
Vector3f[] averageVert = new Vector3f[temporalMesh.getVertices().size()];
|
||||
int[] averageCount = new int[temporalMesh.getVertices().size()];
|
||||
List<CreasePoint> creasePoints = new ArrayList<CreasePoint>(temporalMesh.getVertexCount());
|
||||
for (int i = 0; i < temporalMesh.getVertexCount(); ++i) {
|
||||
// finding adjacent edges that were created by dividing original edges
|
||||
List<Edge> adjacentOriginalEdges = new ArrayList<Edge>();
|
||||
for (Edge edge : temporalMesh.getAdjacentEdges(i)) {
|
||||
if (verticesOnOriginalEdges.contains(edge.getFirstIndex()) || verticesOnOriginalEdges.contains(edge.getSecondIndex())) {
|
||||
adjacentOriginalEdges.add(edge);
|
||||
}
|
||||
}
|
||||
|
||||
creasePoints.add(new CreasePoint(i, boundaryVertices.contains(i), adjacentOriginalEdges, temporalMesh));
|
||||
}
|
||||
|
||||
Vector3f[] averageVert = new Vector3f[temporalMesh.getVertexCount()];
|
||||
int[] averageCount = new int[temporalMesh.getVertexCount()];
|
||||
|
||||
for (Face face : temporalMesh.getFaces()) {
|
||||
Vector3f centroid = face.computeCentroid();
|
||||
@ -144,11 +165,23 @@ public class SubdivisionSurfaceModifier extends Modifier {
|
||||
}
|
||||
|
||||
for (int i = 0; i < averageVert.length; ++i) {
|
||||
Vector3f v = temporalMesh.getVertices().get(i);
|
||||
averageVert[i].divideLocal(averageCount[i]);
|
||||
|
||||
// computing translation vector
|
||||
Vector3f t = averageVert[i].subtract(v);
|
||||
if (!boundaryVertices.contains(i)) {
|
||||
temporalMesh.getVertices().get(i).addLocal(averageVert[i].subtract(temporalMesh.getVertices().get(i)).multLocal(4 / (float) averageCount[i]));
|
||||
} else {
|
||||
temporalMesh.getVertices().get(i).set(averageVert[i]);
|
||||
t.multLocal(4 / (float) averageCount[i]);
|
||||
}
|
||||
|
||||
// moving the vertex
|
||||
v.addLocal(t);
|
||||
|
||||
// applying crease weight if neccessary
|
||||
CreasePoint creasePoint = creasePoints.get(i);
|
||||
if (creasePoint.getTarget() != null && creasePoint.getWeight() != 0) {
|
||||
t = creasePoint.getTarget().subtractLocal(v).multLocal(creasePoint.getWeight());
|
||||
v.addLocal(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -164,6 +197,7 @@ public class SubdivisionSurfaceModifier extends Modifier {
|
||||
Map<Edge, Integer> edgePoints = new HashMap<Edge, Integer>();
|
||||
Map<Face, Integer> facePoints = new HashMap<Face, Integer>();
|
||||
List<Face> newFaces = new ArrayList<Face>();
|
||||
List<Edge> newEdges = new ArrayList<Edge>(temporalMesh.getEdges().size() * 2);
|
||||
|
||||
int originalFacesCount = temporalMesh.getFaces().size();
|
||||
|
||||
@ -198,14 +232,15 @@ public class SubdivisionSurfaceModifier extends Modifier {
|
||||
int vPrevIndex = i == 0 ? face.getIndexes().get(face.getIndexes().size() - 1) : face.getIndexes().get(i - 1);
|
||||
int vNextIndex = i == face.getIndexes().size() - 1 ? face.getIndexes().get(0) : face.getIndexes().get(i + 1);
|
||||
|
||||
Edge prevEdge = this.findEdge(temporalMesh, vPrevIndex, vIndex);// new Edge(vPrevIndex, vIndex, 0, true, temporalMesh.getVertices());
|
||||
Edge nextEdge = this.findEdge(temporalMesh, vIndex, vNextIndex);// new Edge(vIndex, vNextIndex, 0, true, temporalMesh.getVertices());
|
||||
Edge prevEdge = this.findEdge(temporalMesh, vPrevIndex, vIndex);
|
||||
Edge nextEdge = this.findEdge(temporalMesh, vIndex, vNextIndex);
|
||||
int vPrevEdgeVertIndex = edgePoints.containsKey(prevEdge) ? edgePoints.get(prevEdge) : -1;
|
||||
int vNextEdgeVertIndex = edgePoints.containsKey(nextEdge) ? edgePoints.get(nextEdge) : -1;
|
||||
|
||||
Vector3f v = temporalMesh.getVertices().get(vIndex);
|
||||
if (vPrevEdgeVertIndex < 0) {
|
||||
vPrevEdgeVertIndex = vertices.size() + originalFacesCount + edgeVertices.size();
|
||||
verticesOnOriginalEdges.add(vNextEdgeVertIndex);
|
||||
edgeVertices.add(vertices.get(vPrevIndex).add(v).divideLocal(2));
|
||||
edgeNormals.add(normals.get(vPrevIndex).add(normals.get(vIndex)).normalizeLocal());
|
||||
edgePoints.put(prevEdge, vPrevEdgeVertIndex);
|
||||
@ -215,6 +250,7 @@ public class SubdivisionSurfaceModifier extends Modifier {
|
||||
}
|
||||
if (vNextEdgeVertIndex < 0) {
|
||||
vNextEdgeVertIndex = vertices.size() + originalFacesCount + edgeVertices.size();
|
||||
verticesOnOriginalEdges.add(vPrevEdgeVertIndex);
|
||||
edgeVertices.add(vertices.get(vNextIndex).add(v).divideLocal(2));
|
||||
edgeNormals.add(normals.get(vNextIndex).add(normals.get(vIndex)).normalizeLocal());
|
||||
edgePoints.put(nextEdge, vNextEdgeVertIndex);
|
||||
@ -257,6 +293,11 @@ public class SubdivisionSurfaceModifier extends Modifier {
|
||||
}
|
||||
|
||||
newFaces.add(new Face(indexes, face.isSmooth(), face.getMaterialNumber(), newUVSets, vertexColors, temporalMesh));
|
||||
|
||||
newEdges.add(new Edge(vIndex, vNextEdgeVertIndex, nextEdge.getCrease(), true, temporalMesh));
|
||||
newEdges.add(new Edge(vNextEdgeVertIndex, facePointIndex, 0, true, temporalMesh));
|
||||
newEdges.add(new Edge(facePointIndex, vPrevEdgeVertIndex, 0, true, temporalMesh));
|
||||
newEdges.add(new Edge(vPrevEdgeVertIndex, vIndex, prevEdge.getCrease(), true, temporalMesh));
|
||||
}
|
||||
}
|
||||
|
||||
@ -265,24 +306,14 @@ public class SubdivisionSurfaceModifier extends Modifier {
|
||||
normals.addAll(faceNormals);
|
||||
normals.addAll(edgeNormals);
|
||||
|
||||
List<Edge> newEdges = new ArrayList<Edge>(temporalMesh.getEdges().size() * 2);
|
||||
for (Edge edge : temporalMesh.getEdges()) {
|
||||
if (!edge.isInFace()) {
|
||||
int newVertexIndex = vertices.size();
|
||||
vertices.add(vertices.get(edge.getFirstIndex()).add(vertices.get(edge.getSecondIndex())).divideLocal(2));
|
||||
normals.add(normals.get(edge.getFirstIndex()).add(normals.get(edge.getSecondIndex())).normalizeLocal());
|
||||
|
||||
newEdges.add(new Edge(edge.getFirstIndex(), newVertexIndex, 0, false, vertices));
|
||||
newEdges.add(new Edge(newVertexIndex, edge.getSecondIndex(), 0, false, vertices));
|
||||
} else {
|
||||
Integer edgePoint = edgePoints.get(edge);
|
||||
newEdges.add(new Edge(edge.getFirstIndex(), edgePoint, edge.getCrease(), true, vertices));
|
||||
newEdges.add(new Edge(edgePoint, edge.getSecondIndex(), edge.getCrease(), true, vertices));
|
||||
// adding edges between face points and edge points
|
||||
List<Face> facesContainingTheEdge = temporalMesh.getAdjacentFaces(edge);
|
||||
for (Face f : facesContainingTheEdge) {
|
||||
newEdges.add(new Edge(facePoints.get(f), edgePoint, 0, true, vertices));
|
||||
}
|
||||
newEdges.add(new Edge(edge.getFirstIndex(), newVertexIndex, 0, false, temporalMesh));
|
||||
newEdges.add(new Edge(newVertexIndex, edge.getSecondIndex(), 0, false, temporalMesh));
|
||||
}
|
||||
}
|
||||
|
||||
@ -506,9 +537,9 @@ public class SubdivisionSurfaceModifier extends Modifier {
|
||||
}
|
||||
faces.add(new Face(indexes, false, 0, null, null, this));
|
||||
for (i = 1; i < indexes.length; ++i) {
|
||||
edges.add(new Edge(indexes[i - 1], indexes[i], 0, true, vertices));
|
||||
edges.add(new Edge(indexes[i - 1], indexes[i], 0, true, this));
|
||||
}
|
||||
edges.add(new Edge(indexes[indexes.length - 1], indexes[0], 0, true, vertices));
|
||||
edges.add(new Edge(indexes[indexes.length - 1], indexes[0], 0, true, this));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -527,4 +558,61 @@ public class SubdivisionSurfaceModifier extends Modifier {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A point computed for each vertex before applying CC subdivision and after simple subdivision.
|
||||
* This class has a target where the vertices will be drawn to with a proper strength (value from 0 to 1).
|
||||
*
|
||||
* The algorithm of computing the target point was made by observing how blender behaves.
|
||||
* If a vertex has one or less creased edges (means edges that have non zero crease value) the target will not exist.
|
||||
* If a vertex is a border vertex and has two creased edges - the target will be the original simple subdivided vertex.
|
||||
* If a vertex is not a border vertex and have two creased edges - then it will be drawned to the plane defined by those
|
||||
* two edges.
|
||||
* If a vertex has 3 or more creased edges it will be drawn to its original vertex before CC subdivision with average strength
|
||||
* computed from edges' crease values.
|
||||
*
|
||||
* @author Marcin Roguski (Kaelthas)
|
||||
*/
|
||||
private static class CreasePoint {
|
||||
private Vector3f target = new Vector3f();
|
||||
private float weight;
|
||||
|
||||
public CreasePoint(int index, boolean borderIndex, List<Edge> creaseEdges, TemporalMesh temporalMesh) {
|
||||
if (creaseEdges == null || creaseEdges.size() <= 1) {
|
||||
target = null;// crease is used when vertex belongs to at least 2 creased edges
|
||||
} else {
|
||||
int creasedEdgesCount = 0;
|
||||
for (Edge edge : creaseEdges) {
|
||||
if (edge.getCrease() > 0) {
|
||||
++creasedEdgesCount;
|
||||
weight += edge.getCrease();
|
||||
target.addLocal(temporalMesh.getVertices().get(edge.getOtherIndex(index)));
|
||||
}
|
||||
}
|
||||
|
||||
if (creasedEdgesCount <= 1) {
|
||||
target = null;// crease is used when vertex belongs to at least 2 creased edges
|
||||
} else if (creasedEdgesCount == 2) {
|
||||
if (borderIndex) {
|
||||
target.set(temporalMesh.getVertices().get(index));
|
||||
} else {
|
||||
target.divideLocal(creasedEdgesCount);
|
||||
}
|
||||
} else {
|
||||
target.set(temporalMesh.getVertices().get(index));
|
||||
}
|
||||
if (creasedEdgesCount > 0) {
|
||||
weight /= creasedEdgesCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Vector3f getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
public float getWeight() {
|
||||
return weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user