diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/curves/BezierCurve.java b/engine/src/blender/com/jme3/scene/plugins/blender/curves/BezierCurve.java index ebf609646..3bbbd950d 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/curves/BezierCurve.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/curves/BezierCurve.java @@ -9,7 +9,7 @@ import java.util.List; /** * A class that helps to calculate the bezier curves calues. It uses doubles for performing calculations to minimize * floating point operations errors. - * @author Marcin Roguski + * @author Marcin Roguski (Kaelthas) */ public class BezierCurve { @@ -25,6 +25,8 @@ public class BezierCurve { private int dimension; /** A table of the bezier points. */ private float[][][] bezierPoints; + /** Array that stores a radius for each bezier triple. */ + private float[] radiuses; @SuppressWarnings("unchecked") public BezierCurve(final int type, final List bezTriples, final int dimension) { @@ -37,6 +39,7 @@ public class BezierCurve { //the second index points to a table od three points of a bezier triple (handle, point, handle) //the third index specifies the coordinates of the specific point in a bezier triple bezierPoints = new float[bezTriples.size()][3][dimension]; + radiuses = new float[bezTriples.size()]; int i = 0, j, k; for (Structure bezTriple : bezTriples) { DynamicArray vec = (DynamicArray) bezTriple.getFieldValue("vec"); @@ -45,7 +48,7 @@ public class BezierCurve { bezierPoints[i][j][k] = vec.get(j, k).floatValue(); } } - ++i; + radiuses[i++] = ((Number)bezTriple.getFieldValue("radius")).floatValue(); } } @@ -93,6 +96,17 @@ public class BezierCurve { return type; } + /** + * The method returns the radius for the required bezier triple. + * + * @param bezierTripleIndex + * index of the bezier triple + * @return radius of the required bezier triple + */ + public float getRadius(int bezierTripleIndex) { + return radiuses[bezierTripleIndex]; + } + /** * This method returns a list of control points for this curve. * @return a list of control points for this curve. diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/curves/CurvesHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/curves/CurvesHelper.java index 5ea2af60c..9de0b7d8b 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/curves/CurvesHelper.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/curves/CurvesHelper.java @@ -32,12 +32,12 @@ package com.jme3.scene.plugins.blender.curves; import java.nio.FloatBuffer; -import java.nio.IntBuffer; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.TreeMap; import java.util.logging.Logger; import com.jme3.material.Material; @@ -69,11 +69,12 @@ import com.jme3.util.BufferUtils; /** * A class that is used in mesh calculations. - * @author Marcin Roguski + * + * @author Marcin Roguski (Kaelthas) */ public class CurvesHelper extends AbstractBlenderHelper { - private static final Logger LOGGER = Logger.getLogger(CurvesHelper.class.getName()); + /** Minimum basis U function degree for NURBS curves and surfaces. */ protected int minimumBasisUFunctionDegree = 4; /** Minimum basis V function degree for NURBS curves and surfaces. */ @@ -113,7 +114,7 @@ public class CurvesHelper extends AbstractBlenderHelper { if (isBack) { LOGGER.warning("No back face in curve implemented yet!");//TODO: implement back face } - + //reading nurbs (and sorting them by material) List nurbStructures = ((Structure) curveStructure.getFieldValue("nurb")).evaluateListBase(blenderContext); Map> nurbs = new HashMap>(); @@ -155,44 +156,43 @@ public class CurvesHelper extends AbstractBlenderHelper { float handlerLength = bevelDepth / 2.0f; List conrtolPoints = new ArrayList(extrude > 0.0f ? 19 : 13); - conrtolPoints.add(new Vector3f(-bevelDepth, extrude, 0)); - conrtolPoints.add(new Vector3f(-bevelDepth, handlerLength + extrude, 0)); - - conrtolPoints.add(new Vector3f(-handlerLength, bevelDepth + extrude, 0)); - conrtolPoints.add(new Vector3f(0, bevelDepth + extrude, 0)); - conrtolPoints.add(new Vector3f(handlerLength, bevelDepth + extrude, 0)); - - conrtolPoints.add(new Vector3f(bevelDepth, extrude + handlerLength, 0)); - conrtolPoints.add(new Vector3f(bevelDepth, extrude, 0)); - conrtolPoints.add(new Vector3f(bevelDepth, extrude - handlerLength, 0)); - if (extrude > 0.0f) { - conrtolPoints.add(new Vector3f(bevelDepth, -extrude + handlerLength, 0)); - conrtolPoints.add(new Vector3f(bevelDepth, -extrude, 0)); - conrtolPoints.add(new Vector3f(bevelDepth, -extrude - handlerLength, 0)); + conrtolPoints.add(new Vector3f(-bevelDepth, 0, extrude)); + conrtolPoints.add(new Vector3f(-bevelDepth, 0, -handlerLength + extrude)); + conrtolPoints.add(new Vector3f(-bevelDepth, 0, handlerLength - extrude)); } - - conrtolPoints.add(new Vector3f(handlerLength, -bevelDepth - extrude, 0)); - conrtolPoints.add(new Vector3f(0, -bevelDepth - extrude, 0)); - conrtolPoints.add(new Vector3f(-handlerLength, -bevelDepth - extrude, 0)); - - conrtolPoints.add(new Vector3f(-bevelDepth, -handlerLength - extrude, 0)); - conrtolPoints.add(new Vector3f(-bevelDepth, -extrude, 0)); - + + conrtolPoints.add(new Vector3f(-bevelDepth, 0, -extrude)); + conrtolPoints.add(new Vector3f(-bevelDepth, 0, -handlerLength - extrude)); + + conrtolPoints.add(new Vector3f(-handlerLength, 0, -bevelDepth - extrude)); + conrtolPoints.add(new Vector3f(0, 0, -bevelDepth - extrude)); + conrtolPoints.add(new Vector3f(handlerLength, 0, -bevelDepth - extrude)); + if (extrude > 0.0f) { - conrtolPoints.add(new Vector3f(-bevelDepth, handlerLength - extrude, 0)); - - conrtolPoints.add(new Vector3f(-bevelDepth, -handlerLength + extrude, 0)); - conrtolPoints.add(new Vector3f(-bevelDepth, extrude, 0)); + conrtolPoints.add(new Vector3f(bevelDepth, 0, -extrude - handlerLength)); + conrtolPoints.add(new Vector3f(bevelDepth, 0, -extrude)); + conrtolPoints.add(new Vector3f(bevelDepth, 0, -extrude + handlerLength)); } - + + conrtolPoints.add(new Vector3f(bevelDepth, 0, extrude - handlerLength)); + conrtolPoints.add(new Vector3f(bevelDepth, 0, extrude)); + conrtolPoints.add(new Vector3f(bevelDepth, 0, extrude + handlerLength)); + + conrtolPoints.add(new Vector3f(handlerLength, 0, bevelDepth + extrude)); + conrtolPoints.add(new Vector3f(0, 0, bevelDepth + extrude)); + conrtolPoints.add(new Vector3f(-handlerLength, 0, bevelDepth + extrude)); + + conrtolPoints.add(new Vector3f(-bevelDepth, 0, handlerLength + extrude)); + conrtolPoints.add(new Vector3f(-bevelDepth, 0, extrude)); + Spline bevelSpline = new Spline(SplineType.Bezier, conrtolPoints, 0, false); Curve bevelCurve = new Curve(bevelSpline, bevResol); bevelObject = new ArrayList(1); bevelObject.add(new Geometry("", bevelCurve)); } else if (extrude > 0.0f) { Spline bevelSpline = new Spline(SplineType.Linear, new Vector3f[]{ - new Vector3f(0, extrude, 0), new Vector3f(0, -extrude, 0) + new Vector3f(0, 0, -extrude), new Vector3f(0, 0, extrude) }, 1, false); Curve bevelCurve = new Curve(bevelSpline, bevResol); bevelObject = new ArrayList(1); @@ -201,7 +201,7 @@ public class CurvesHelper extends AbstractBlenderHelper { } //getting taper object - Curve taperObject = null; + Spline taperObject = null; Pointer pTaperObject = (Pointer) curveStructure.getFieldValue("taperobj"); if (bevelObject != null && pTaperObject.isNotNull()) { Pointer pTaperStructure = (Pointer) pTaperObject.fetchData(blenderContext.getInputStream()).get(0).getFieldValue("data"); @@ -262,7 +262,7 @@ public class CurvesHelper extends AbstractBlenderHelper { * @throws BlenderFileException * an exception is thrown when there are problems with the blender file */ - protected List loadBezierCurve(Vector3f loc, Structure nurb, List bevelObject, Curve taperObject, + protected List loadBezierCurve(Vector3f loc, Structure nurb, List bevelObject, Spline taperObject, BlenderContext blenderContext) throws BlenderFileException { Pointer pBezierTriple = (Pointer) nurb.getFieldValue("bezt"); List result = new ArrayList(); @@ -274,6 +274,23 @@ public class CurvesHelper extends AbstractBlenderHelper { //creating the curve object BezierCurve bezierCurve = new BezierCurve(0, pBezierTriple.fetchData(blenderContext.getInputStream()), 3); List controlPoints = bezierCurve.getControlPoints(); + if(fixUpAxis) { + for(Vector3f v : controlPoints) { + float y = v.y; + v.y = v.z; + v.z = -y; + } + } + + if (bevelObject != null && taperObject == null) {// create taper object using the scales of the bezier triple + int triplesCount = controlPoints.size() / 3; + List taperControlPoints = new ArrayList(triplesCount); + for (int i = 0; i < triplesCount; ++i) { + taperControlPoints.add(new Vector3f(controlPoints.get(i * 3 + 1).x, bezierCurve.getRadius(i), 0)); + } + taperObject = new Spline(SplineType.Linear, taperControlPoints, 0, false); + } + if (cyclic) { //copy the first three points at the end for (int i = 0; i < 3; ++i) { @@ -292,7 +309,7 @@ public class CurvesHelper extends AbstractBlenderHelper { result.add(curveGeometry); //TODO: use front and back flags; surface excluding algorithm for bezier circles should be added } else {//creating curve with bevel and taper shape - result = this.applyBevelAndTaper(curve, bevelObject, taperObject, smooth, blenderContext); + result = this.applyBevelAndTaper(curve, bevelObject, taperObject, smooth, blenderContext); } } return result; @@ -315,7 +332,7 @@ public class CurvesHelper extends AbstractBlenderHelper { * an exception is throw when problems with blender loaded data occurs */ @SuppressWarnings("unchecked") - protected List loadNurb(Vector3f loc, Structure nurb, List bevelObject, Curve taperObject, + protected List loadNurb(Vector3f loc, Structure nurb, List bevelObject, Spline taperObject, BlenderContext blenderContext) throws BlenderFileException { //loading the knots List[] knots = new List[2]; @@ -388,34 +405,41 @@ public class CurvesHelper extends AbstractBlenderHelper { } return result; } - - /** - * This method returns the taper scale that should be applied to the object. - * @param taperPoints - * the taper points - * @param taperLength - * the taper curve length - * @param percent - * the percent of way along the whole taper curve - */ - protected float getTaperScale(float[] taperPoints, float taperLength, float percent) { - float length = taperLength * percent; - float currentLength = 0; - Vector3f p = new Vector3f(); - int i; - for (i = 0; i < taperPoints.length - 6 && currentLength < length; i += 3) { - p.set(taperPoints[i], taperPoints[i + 1], taperPoints[i + 2]); - p.subtractLocal(taperPoints[i + 3], taperPoints[i + 4], taperPoints[i + 5]); - currentLength += p.length(); - } - currentLength -= p.length(); - float leftLength = length - currentLength; - float percentOnSegment = p.length() == 0 ? 0 : leftLength / p.length(); - Vector3f store = FastMath.interpolateLinear(percentOnSegment, - new Vector3f(taperPoints[i], taperPoints[i + 1], taperPoints[i + 2]), - new Vector3f(taperPoints[i + 3], taperPoints[i + 4], taperPoints[i + 5])); - return store.y; - } + + /** + * The method computes the taper scale on the given point on the curve. + * + * @param taper + * the taper object that defines the scale + * @param percent + * the percent of the 'road' along the curve + * @return scale on the pointed place along the curve + */ + protected float getTaperScale(Spline taper, float percent) { + percent = FastMath.clamp(percent, 0, 1); + List segmentLengths = taper.getSegmentsLength(); + float percentLength = taper.getTotalLength() * percent; + float partLength = 0; + int i; + for (i = 0; i < segmentLengths.size(); ++i) { + partLength += segmentLengths.get(i); + if (partLength > percentLength) { + partLength -= segmentLengths.get(i); + percentLength -= partLength; + percent = percentLength / segmentLengths.get(i); + break; + } + } + // do not cross the line :) + if (percent >= 1) { + percent = 1; + --i; + } + if (taper.getType() == SplineType.Bezier) { + i *= 3; + } + return taper.interpolate(percent, i, null).y; + } /** * This method applies bevel and taper objects to the curve. @@ -431,186 +455,325 @@ public class CurvesHelper extends AbstractBlenderHelper { * the blender context * @return a list of geometries representing the beveled and/or tapered curve */ - protected List applyBevelAndTaper(Curve curve, List bevelObject, Curve taperObject, + protected List applyBevelAndTaper(Curve curve, List bevelObject, Spline taperObject, boolean smooth, BlenderContext blenderContext) { - float[] curvePoints = BufferUtils.getFloatArray(curve.getFloatBuffer(Type.Position)); + Vector3f[] curvePoints = BufferUtils.getVector3Array(curve.getFloatBuffer(Type.Position)); float curveLength = curve.getLength(); - //TODO: use the smooth var - - //taper data - float[] taperPoints = null; - float taperLength = 0; - if (taperObject != null) { - taperPoints = BufferUtils.getFloatArray(taperObject.getFloatBuffer(Type.Position)); - taperLength = taperObject.getLength(); - } - - //several objects can be allocated only once - Vector3f p = new Vector3f(); - Vector3f z = new Vector3f(0, 0, 1); - Vector3f negativeY = new Vector3f(0, -1, 0); - Matrix4f m = new Matrix4f(); - float lengthAlongCurve = 0, taperScale = 1.0f; - Quaternion planeRotation = new Quaternion(); - Quaternion zRotation = new Quaternion(); - float[] temp = new float[]{0, 0, 0, 1}; - Map normalMap = new HashMap();//normalMap merges normals of faces that will be rendered smooth - + FloatBuffer[] vertexBuffers = new FloatBuffer[bevelObject.size()]; FloatBuffer[] normalBuffers = new FloatBuffer[bevelObject.size()]; - IntBuffer[] indexBuffers = new IntBuffer[bevelObject.size()]; - for (int geomIndex = 0; geomIndex < bevelObject.size(); ++geomIndex) { - Mesh mesh = bevelObject.get(geomIndex).getMesh(); - FloatBuffer positions = mesh.getFloatBuffer(Type.Position); - float[] vertices = BufferUtils.getFloatArray(positions); - - for (int i = 0; i < curvePoints.length; i += 3) { - p.set(curvePoints[i], curvePoints[i + 1], curvePoints[i + 2]); - Vector3f v; - if (i == 0) { - v = new Vector3f(curvePoints[3] - p.x, curvePoints[4] - p.y, curvePoints[5] - p.z); - } else if (i + 3 >= curvePoints.length) { - v = new Vector3f(p.x - curvePoints[i - 3], p.y - curvePoints[i - 2], p.z - curvePoints[i - 1]); - lengthAlongCurve += v.length(); - } else { - v = new Vector3f(curvePoints[i + 3] - curvePoints[i - 3], - curvePoints[i + 4] - curvePoints[i - 2], - curvePoints[i + 5] - curvePoints[i - 1]); - lengthAlongCurve += new Vector3f(curvePoints[i + 3] - p.x, curvePoints[i + 4] - p.y, curvePoints[i + 5] - p.z).length(); - } - v.normalizeLocal(); - - float angle = FastMath.acos(v.dot(z)); - v.crossLocal(z).normalizeLocal();//v is the rotation axis now - planeRotation.fromAngleAxis(angle, v); - - Vector3f zAxisRotationVector = negativeY.cross(v).normalizeLocal(); - float zAxisRotationAngle = FastMath.acos(negativeY.dot(v)); - zRotation.fromAngleAxis(zAxisRotationAngle, zAxisRotationVector); - - //point transformation matrix - if (taperPoints != null) { - taperScale = this.getTaperScale(taperPoints, taperLength, lengthAlongCurve / curveLength); - } - m.set(Matrix4f.IDENTITY); - m.setRotationQuaternion(planeRotation.multLocal(zRotation)); - m.setTranslation(p); - - //these vertices need to be thrown on XY plane - //and moved to the origin of [p1.x, p1.y] on the plane - Vector3f[] verts = new Vector3f[vertices.length / 3]; - for (int j = 0; j < verts.length; ++j) { - temp[0] = vertices[j * 3] * taperScale; - temp[1] = vertices[j * 3 + 1] * taperScale; - temp[2] = 0; - m.mult(temp);//the result is stored in the array - if (fixUpAxis) {//TODO: not the other way ??? - verts[j] = new Vector3f(temp[0], temp[1], temp[2]); - } else { - verts[j] = new Vector3f(temp[0], temp[2], -temp[1]); - } - } - if (vertexBuffers[geomIndex] == null) { - vertexBuffers[geomIndex] = BufferUtils.createFloatBuffer(verts.length * curvePoints.length); - } - FloatBuffer buffer = BufferUtils.createFloatBuffer(verts); - vertexBuffers[geomIndex].put(buffer); - - //adding indexes - IntBuffer indexBuffer = indexBuffers[geomIndex]; - if (indexBuffer == null) { - //the amount of faces in the final mesh is the amount of edges in the bevel curve - //(which is less by 1 than its number of vertices) - //multiplied by 2 (because each edge has two faces assigned on both sides) - //and multiplied by the amount of bevel curve repeats which is equal to the amount of vertices on the target curve - //finally we need to subtract the bevel edges amount 2 times because the border edges have only one face attached - //and at last multiply everything by 3 because each face needs 3 indexes to be described - int bevelCurveEdgesAmount = verts.length - 1; - indexBuffer = BufferUtils.createIntBuffer(((bevelCurveEdgesAmount << 1) * curvePoints.length - bevelCurveEdgesAmount << 1) * 3); - indexBuffers[geomIndex] = indexBuffer; - } - int pointOffset = i / 3 * verts.length; - if (i + 3 < curvePoints.length) { - for (int index = 0; index < verts.length - 1; ++index) { - indexBuffer.put(index + pointOffset); - indexBuffer.put(index + pointOffset + 1); - indexBuffer.put(verts.length + index + pointOffset); - indexBuffer.put(verts.length + index + pointOffset); - indexBuffer.put(index + pointOffset + 1); - indexBuffer.put(verts.length + index + pointOffset + 1); - } - } - } - } - - //calculating the normals + IndexBuffer[] indexBuffers = new IndexBuffer[bevelObject.size()]; for (int geomIndex = 0; geomIndex < bevelObject.size(); ++geomIndex) { - Vector3f[] allVerts = BufferUtils.getVector3Array(vertexBuffers[geomIndex]); - int[] allIndices = BufferUtils.getIntArray(indexBuffers[geomIndex]); - for (int i = 0; i < allIndices.length - 3; i += 3) { - Vector3f n = FastMath.computeNormal(allVerts[allIndices[i]], allVerts[allIndices[i + 1]], allVerts[allIndices[i + 2]]); - this.addNormal(n, normalMap, smooth, allVerts[allIndices[i]], allVerts[allIndices[i + 1]], allVerts[allIndices[i + 2]]); - } - if (normalBuffers[geomIndex] == null) { - normalBuffers[geomIndex] = BufferUtils.createFloatBuffer(allVerts.length * 3); - } - for (Vector3f v : allVerts) { - Vector3f n = normalMap.get(v); - normalBuffers[geomIndex].put(n.x); - normalBuffers[geomIndex].put(n.y); - normalBuffers[geomIndex].put(n.z); + Mesh mesh = bevelObject.get(geomIndex).getMesh(); + Vector3f[] positions = BufferUtils.getVector3Array(mesh.getFloatBuffer(Type.Position)); + Vector3f[] bevelPoints = this.transformToFirstLineOfBevelPoints(positions, curvePoints[0], curvePoints[1]); + + List bevels = new ArrayList(curvePoints.length); + bevels.add(bevelPoints); + + vertexBuffers[geomIndex] = BufferUtils.createFloatBuffer(bevelPoints.length * 3 * curvePoints.length * (smooth ? 1 : 6)); + for (int i = 1; i < curvePoints.length - 1; ++i) { + bevelPoints = this.transformBevel(bevelPoints, curvePoints[i - 1], curvePoints[i], curvePoints[i + 1]); + bevels.add(bevelPoints); } + bevelPoints = this.transformBevel(bevelPoints, curvePoints[curvePoints.length - 2], curvePoints[curvePoints.length - 1], null); + bevels.add(bevelPoints); + + //apply scales to the bevels + float lengthAlongCurve = 0; + for(int i=0;i 0) { + lengthAlongCurve += curvePoints[i].subtract(curvePoints[i - 1]).length(); + } + float taperScale = this.getTaperScale(taperObject, i == 0 ? 0 : lengthAlongCurve / curveLength); + this.applyScale(bevels.get(i), curvePoints[i], taperScale); + } + + if(smooth) {//add everything to the buffer + for(Vector3f[] bevel : bevels) { + for(Vector3f d : bevel) { + vertexBuffers[geomIndex].put(d.x); + vertexBuffers[geomIndex].put(d.y); + vertexBuffers[geomIndex].put(d.z); + } + } + } else {//add vertices to the buffer duplicating them so that every vertex belongs only to a single triangle + for (int i = 0; i < curvePoints.length - 1; ++i) { + for (int j = 0; j < bevelPoints.length - 1; ++j) { + //first triangle + vertexBuffers[geomIndex].put(bevels.get(i)[j].x); + vertexBuffers[geomIndex].put(bevels.get(i)[j].y); + vertexBuffers[geomIndex].put(bevels.get(i)[j].z); + vertexBuffers[geomIndex].put(bevels.get(i)[j + 1].x); + vertexBuffers[geomIndex].put(bevels.get(i)[j + 1].y); + vertexBuffers[geomIndex].put(bevels.get(i)[j + 1].z); + vertexBuffers[geomIndex].put(bevels.get(i + 1)[j].x); + vertexBuffers[geomIndex].put(bevels.get(i + 1)[j].y); + vertexBuffers[geomIndex].put(bevels.get(i + 1)[j].z); + + //second triangle + vertexBuffers[geomIndex].put(bevels.get(i)[j + 1].x); + vertexBuffers[geomIndex].put(bevels.get(i)[j + 1].y); + vertexBuffers[geomIndex].put(bevels.get(i)[j + 1].z); + vertexBuffers[geomIndex].put(bevels.get(i + 1)[j + 1].x); + vertexBuffers[geomIndex].put(bevels.get(i + 1)[j + 1].y); + vertexBuffers[geomIndex].put(bevels.get(i + 1)[j + 1].z); + vertexBuffers[geomIndex].put(bevels.get(i + 1)[j].x); + vertexBuffers[geomIndex].put(bevels.get(i + 1)[j].y); + vertexBuffers[geomIndex].put(bevels.get(i + 1)[j].z); + } + } + } + + indexBuffers[geomIndex] = this.generateIndexes(bevelPoints.length, curvePoints.length, smooth); + normalBuffers[geomIndex] = this.generateNormals(indexBuffers[geomIndex], vertexBuffers[geomIndex], smooth); } - + + //creating and returning the result List result = new ArrayList(vertexBuffers.length); Float oneReferenceToCurveLength = new Float(curveLength);//its important for array modifier to use one reference here for (int i = 0; i < vertexBuffers.length; ++i) { Mesh mesh = new Mesh(); mesh.setBuffer(Type.Position, 3, vertexBuffers[i]); - mesh.setBuffer(Type.Index, 3, indexBuffers[i]); + if(indexBuffers[i].getIntBuffer() != null) { + mesh.setBuffer(Type.Index, 3, indexBuffers[i].getIntBuffer()); + } else { + mesh.setBuffer(Type.Index, 3, indexBuffers[i].getShortBuffer()); + } mesh.setBuffer(Type.Normal, 3, normalBuffers[i]); Geometry g = new Geometry("g" + i, mesh); g.setUserData("curveLength", oneReferenceToCurveLength); g.updateModelBound(); result.add(g); } - return result; } + + /** + * the method applies scale for the given bevel points. The points table is + * being modified so expect ypur result there. + * + * @param points + * the bevel points + * @param centerPoint + * the center point of the bevel + * @param scale + * the scale to be applied + */ + private void applyScale(Vector3f[] points, Vector3f centerPoint, float scale) { + Vector3f taperScaleVector = new Vector3f(); + for (Vector3f p : points) { + taperScaleVector.set(centerPoint).subtractLocal(p).multLocal(1 - scale); + p.addLocal(taperScaleVector); + } + } + + /** + * The method generates normal buffer for the created mesh of the curve. + * + * @param indexes + * the indexes of the mesh points + * @param points + * the mesh's points + * @param smooth + * the flag indicating if the result is to be smooth or solid + * @return normals buffer for the mesh + */ + private FloatBuffer generateNormals(IndexBuffer indexes, FloatBuffer points, boolean smooth) { + Map normalMap = new TreeMap(); + Vector3f[] allVerts = BufferUtils.getVector3Array(points); + + for (int i = 0; i < indexes.limit(); i += 3) { + int index1 = indexes.get(i); + int index2 = indexes.get(i + 1); + int index3 = indexes.get(i + 2); + + Vector3f n = FastMath.computeNormal(allVerts[index1], allVerts[index2], allVerts[index3]); + this.addNormal(n, normalMap, smooth, index1, index2, index3); + } - /** - * This method adds a normal to a normals' map. This map is used to merge normals of a vertor that should be rendered smooth. - * - * @param normalToAdd - * a normal to be added - * @param normalMap - * merges normals of faces that will be rendered smooth; the key is the vertex and the value - its normal vector - * @param smooth - * the variable that indicates wheather to merge normals (creating the smooth mesh) or not - * @param vertices - * a list of vertices read from the blender file - */ - private void addNormal(Vector3f normalToAdd, Map normalMap, boolean smooth, Vector3f... vertices) { - for (Vector3f v : vertices) { - Vector3f n = normalMap.get(v); + FloatBuffer normals = BufferUtils.createFloatBuffer(normalMap.size() * 3); + for (Entry entry : normalMap.entrySet()) { + normals.put(entry.getValue().x); + normals.put(entry.getValue().y); + normals.put(entry.getValue().z); + } + return normals; + } + + /** + * The amount of faces in the final mesh is the amount of edges in the bevel + * curve (which is less by 1 than its number of vertices) multiplied by 2 + * (because each edge has two faces assigned on both sides) and multiplied + * by the amount of bevel curve repeats which is equal to the amount of + * vertices on the target curve finally we need to subtract the bevel edges + * amount 2 times because the border edges have only one face attached and + * at last multiply everything by 3 because each face needs 3 indexes to be + * described + * + * @param bevelShapeVertexCount + * amount of points in bevel shape + * @param bevelRepeats + * amount of bevel shapes along the curve + * @param smooth + * the smooth flag + * @return index buffer for the mesh + */ + private IndexBuffer generateIndexes(int bevelShapeVertexCount, int bevelRepeats, boolean smooth) { + if(smooth) { + int indexBufferSize = (bevelRepeats - 1) * (bevelShapeVertexCount - 1) * 6; + IndexBuffer result = new IndexBuffer(indexBufferSize); + + for (int i = 0; i < bevelRepeats - 1; ++i) { + for (int j = 0; j < bevelShapeVertexCount - 1; ++j) { + result.put(i * bevelShapeVertexCount + j); + result.put(i * bevelShapeVertexCount + j + 1); + result.put((i + 1) * bevelShapeVertexCount + j); + + result.put(i * bevelShapeVertexCount + j + 1); + result.put((i + 1) * bevelShapeVertexCount + j + 1); + result.put((i + 1) * bevelShapeVertexCount + j); + } + } + return result; + } else { + //every pair of bevel vertices belongs to two triangles + //we have the same amount of pairs as the amount of vertices in bevel + //so the amount of triangles is: bevelShapeVertexCount * 2 * (bevelRepeats - 1) + //and this gives the amount of vertices in non smooth shape as below ... + int indexBufferSize = bevelShapeVertexCount * bevelRepeats * 6;//6 = 2 * 3 where 2 is stated above and 3 is the count of vertices for each triangle + IndexBuffer result = new IndexBuffer(indexBufferSize); + for (int i = 0; i < indexBufferSize; ++i) { + result.put(i); + } + return result; + } + } + + /** + * The method transforms the bevel along the curve. + * + * @param bevel + * the bevel to be transformed + * @param prevPos + * previous curve point + * @param currPos + * current curve point (here the center of the new bevel will be + * set) + * @param nextPos + * next curve point + * @return points of transformed bevel + */ + private Vector3f[] transformBevel(Vector3f[] bevel, Vector3f prevPos, Vector3f currPos, Vector3f nextPos) { + bevel = bevel.clone(); + + //currPos and directionVector define the line in 3D space + Vector3f directionVector = prevPos != null ? currPos.subtract(prevPos) : nextPos.subtract(currPos); + directionVector.normalizeLocal(); + + //plane is described by equation: Ax + By + Cz + D = 0 where planeNormal = [A, B, C] and D = -(Ax + By + Cz) + Vector3f planeNormal = null; + if(prevPos != null) { + planeNormal = currPos.subtract(prevPos).normalizeLocal(); + if(nextPos != null) { + planeNormal.addLocal(nextPos.subtract(currPos).normalizeLocal()).normalizeLocal(); + } + } else { + planeNormal = nextPos.subtract(currPos).normalizeLocal(); + } + float D = -planeNormal.dot(currPos);//D = -(Ax + By + Cz) + + //now we need to compute paralell cast of each bevel point on the plane, the leading line is already known + //parametric equation of a line: x = px + vx * t; y = py + vy * t; z = pz + vz * t + //where p = currPos and v = directionVector + //using x, y and z in plane equation we get value of 't' that will allow us to compute the point where plane and line cross + float temp = planeNormal.dot(directionVector); + for(int i=0;i normalMap, boolean smooth, int... indexes) { + for (int index : indexes) { + Vector3f n = normalMap.get(index); if (!smooth || n == null) { - normalMap.put(v, normalToAdd.clone()); + normalMap.put(index, normalToAdd.clone()); } else { n.addLocal(normalToAdd).normalizeLocal(); } } } - /** - * This method loads the taper object. - * @param taperStructure - * the taper structure - * @param blenderContext - * the blender context - * @return the taper object - * @throws BlenderFileException - */ - protected Curve loadTaperObject(Structure taperStructure, BlenderContext blenderContext) throws BlenderFileException { + /** + * This method loads the taper object. + * + * @param taperStructure + * the taper structure + * @param blenderContext + * the blender context + * @return the taper object + * @throws BlenderFileException + */ + protected Spline loadTaperObject(Structure taperStructure, BlenderContext blenderContext) throws BlenderFileException { //reading nurbs List nurbStructures = ((Structure) taperStructure.getFieldValue("nurb")).evaluateListBase(blenderContext); for (Structure nurb : nurbStructures) { @@ -625,21 +788,21 @@ public class CurvesHelper extends AbstractBlenderHelper { //return the first taper curve that has more than 3 control points if (controlPoints.size() > 3) { - Spline spline = new Spline(SplineType.Bezier, controlPoints, 0, false); - int resolution = ((Number) taperStructure.getFieldValue("resolu")).intValue(); - return new Curve(spline, resolution); + return new Spline(SplineType.Bezier, controlPoints, 0, false); } } } return null; } - /** - * This method returns the translation of the curve. The UP axis is taken into account here. - * @param curveStructure - * the curve structure - * @return curve translation - */ + /** + * This method returns the translation of the curve. The UP axis is taken + * into account here. + * + * @param curveStructure + * the curve structure + * @return curve translation + */ @SuppressWarnings("unchecked") protected Vector3f getLoc(Structure curveStructure) { DynamicArray locArray = (DynamicArray) curveStructure.getFieldValue("loc"); diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/curves/IndexBuffer.java b/engine/src/blender/com/jme3/scene/plugins/blender/curves/IndexBuffer.java new file mode 100644 index 000000000..84ac99ac1 --- /dev/null +++ b/engine/src/blender/com/jme3/scene/plugins/blender/curves/IndexBuffer.java @@ -0,0 +1,86 @@ +package com.jme3.scene.plugins.blender.curves; + +import java.nio.IntBuffer; +import java.nio.ShortBuffer; + +import com.jme3.util.BufferUtils; + +/** + * A simple helping class to create index buffer. Depending on the size of the + * buffer either ShorBuffer or IntBuffer is used to save memory. + * + * @author Marcin Roguski (Kaelthas) + */ +/* package */class IndexBuffer { + /** The buffer used for larger objects. */ + private IntBuffer intBuffer; + /** The buffer used for smaller objects. */ + private ShortBuffer shortBuffer; + + /** + * Creates the buffer depending on the given size. + * + * @param bufferSize + * the size of the buffer + */ + public IndexBuffer(int bufferSize) { + if (bufferSize < Short.MAX_VALUE) { + shortBuffer = BufferUtils.createShortBuffer(bufferSize); + } else { + intBuffer = BufferUtils.createIntBuffer(bufferSize); + } + } + + /** + * Puts a value to the created buffer. + * + * @param value + * the value to be put to the buffer + */ + public void put(int value) { + if (intBuffer != null) { + intBuffer.put(value); + } else { + shortBuffer.put((short) value); + } + } + + /** + * Returns the value on the given index. Take in mind that onlye int + * is returned, no matter if short or int buffer is used. + * + * @param index + * the index of the value + * @return the value from the buffer + */ + public int get(int index) { + if (intBuffer != null) { + return intBuffer.get(index); + } + return shortBuffer.get(index); + } + + /** + * @return the limit of the buffer + */ + public int limit() { + if (intBuffer != null) { + return intBuffer.limit(); + } + return shortBuffer.limit(); + } + + /** + * @return integer buffer + */ + public IntBuffer getIntBuffer() { + return intBuffer; + } + + /** + * @return short buffer + */ + public ShortBuffer getShortBuffer() { + return shortBuffer; + } +}