From c90e50bb2e9e2be4e00044ab0225f2927ef7698a Mon Sep 17 00:00:00 2001 From: jmekaelthas <76w1dxnh> Date: Tue, 27 Jan 2015 09:45:14 +0100 Subject: [PATCH] Feature: curves and surfaces now have their own temporal mesh which allows to apply modifiers on them. --- .../plugins/blender/curves/BezierCurve.java | 19 +- .../plugins/blender/curves/CurvesHelper.java | 707 +------------- .../blender/curves/CurvesTemporalMesh.java | 890 ++++++++++++++++++ .../scene/plugins/blender/file/Structure.java | 14 +- .../scene/plugins/blender/meshes/Edge.java | 41 +- .../plugins/blender/meshes/TemporalMesh.java | 65 +- .../plugins/blender/objects/ObjectHelper.java | 6 +- 7 files changed, 1007 insertions(+), 735 deletions(-) create mode 100644 jme3-blender/src/main/java/com/jme3/scene/plugins/blender/curves/CurvesTemporalMesh.java diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/curves/BezierCurve.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/curves/BezierCurve.java index b905faee8..9876702c8 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/curves/BezierCurve.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/curves/BezierCurve.java @@ -28,8 +28,12 @@ public class BezierCurve { /** Array that stores a radius for each bezier triple. */ private double[] radiuses; - @SuppressWarnings("unchecked") public BezierCurve(final int type, final List bezTriples, final int dimension) { + this(type, bezTriples, dimension, false); + } + + @SuppressWarnings("unchecked") + public BezierCurve(final int type, final List bezTriples, final int dimension, boolean fixUpAxis) { if (dimension != 2 && dimension != 3) { throw new IllegalArgumentException("The dimension of the curve should be 2 or 3!"); } @@ -45,7 +49,12 @@ public class BezierCurve { DynamicArray vec = (DynamicArray) bezTriple.getFieldValue("vec"); for (j = 0; j < 3; ++j) { for (k = 0; k < dimension; ++k) { - bezierPoints[i][j][k] = vec.get(j, k).floatValue(); + bezierPoints[i][j][k] = vec.get(j, k).doubleValue(); + } + if (fixUpAxis && dimension == 3) { + double temp = bezierPoints[i][j][2]; + bezierPoints[i][j][2] = -bezierPoints[i][j][1]; + bezierPoints[i][j][1] = temp; } } radiuses[i++] = ((Number) bezTriple.getFieldValue("radius")).floatValue(); @@ -114,9 +123,9 @@ public class BezierCurve { public List getControlPoints() { List controlPoints = new ArrayList(bezierPoints.length * 3); for (int i = 0; i < bezierPoints.length; ++i) { - controlPoints.add(new Vector3f((float)bezierPoints[i][0][0], (float)bezierPoints[i][0][1], (float)bezierPoints[i][0][2])); - controlPoints.add(new Vector3f((float)bezierPoints[i][1][0], (float)bezierPoints[i][1][1], (float)bezierPoints[i][1][2])); - controlPoints.add(new Vector3f((float)bezierPoints[i][2][0], (float)bezierPoints[i][2][1], (float)bezierPoints[i][2][2])); + controlPoints.add(new Vector3f((float) bezierPoints[i][0][0], (float) bezierPoints[i][0][1], (float) bezierPoints[i][0][2])); + controlPoints.add(new Vector3f((float) bezierPoints[i][1][0], (float) bezierPoints[i][1][1], (float) bezierPoints[i][1][2])); + controlPoints.add(new Vector3f((float) bezierPoints[i][2][0], (float) bezierPoints[i][2][1], (float) bezierPoints[i][2][2])); } return controlPoints; } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/curves/CurvesHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/curves/CurvesHelper.java index 1be2c5b5a..dd033d3dc 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/curves/CurvesHelper.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/curves/CurvesHelper.java @@ -31,44 +31,16 @@ */ package com.jme3.scene.plugins.blender.curves; -import java.nio.FloatBuffer; -import java.nio.IntBuffer; -import java.nio.ShortBuffer; -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; -import com.jme3.material.RenderState.FaceCullMode; import com.jme3.math.FastMath; import com.jme3.math.Matrix4f; import com.jme3.math.Quaternion; -import com.jme3.math.Spline; -import com.jme3.math.Spline.SplineType; import com.jme3.math.Vector3f; -import com.jme3.math.Vector4f; -import com.jme3.scene.Geometry; -import com.jme3.scene.Mesh; -import com.jme3.scene.VertexBuffer.Type; -import com.jme3.scene.mesh.IndexBuffer; import com.jme3.scene.plugins.blender.AbstractBlenderHelper; import com.jme3.scene.plugins.blender.BlenderContext; import com.jme3.scene.plugins.blender.file.BlenderFileException; -import com.jme3.scene.plugins.blender.file.BlenderInputStream; -import com.jme3.scene.plugins.blender.file.DynamicArray; -import com.jme3.scene.plugins.blender.file.FileBlockHeader; -import com.jme3.scene.plugins.blender.file.Pointer; import com.jme3.scene.plugins.blender.file.Structure; -import com.jme3.scene.plugins.blender.materials.MaterialContext; -import com.jme3.scene.plugins.blender.materials.MaterialHelper; -import com.jme3.scene.plugins.blender.objects.Properties; -import com.jme3.scene.shape.Curve; -import com.jme3.scene.shape.Surface; -import com.jme3.util.BufferUtils; /** * A class that is used in mesh calculations. @@ -95,586 +67,17 @@ public class CurvesHelper extends AbstractBlenderHelper { super(blenderVersion, blenderContext); } - /** - * This method converts given curve structure into a list of geometries representing the curve. The list is used here because on object - * can have several separate curves. - * @param curveStructure - * the curve structure - * @param blenderContext - * the blender context - * @return a list of geometries repreenting a single curve object - * @throws BlenderFileException - */ - public List toCurve(Structure curveStructure, BlenderContext blenderContext) throws BlenderFileException { - String name = curveStructure.getName(); - int flag = ((Number) curveStructure.getFieldValue("flag")).intValue(); - boolean is3D = (flag & 0x01) != 0; - boolean isFront = (flag & 0x02) != 0 && !is3D; - boolean isBack = (flag & 0x04) != 0 && !is3D; - if (isFront) { - LOGGER.warning("No front face in curve implemented yet!");// TODO: implement front face - } - 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(); - Map> nurbs = new HashMap>(); - for (Structure nurb : nurbStructures) { - Number matNumber = (Number) nurb.getFieldValue("mat_nr"); - List nurbList = nurbs.get(matNumber); - if (nurbList == null) { - nurbList = new ArrayList(); - nurbs.put(matNumber, nurbList); - } - nurbList.add(nurb); - } - - // getting materials - MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class); - MaterialContext[] materialContexts = materialHelper.getMaterials(curveStructure, blenderContext); - Material defaultMaterial = null; - if (materialContexts != null) { - for (MaterialContext materialContext : materialContexts) { - materialContext.setFaceCullMode(FaceCullMode.Off); - } - } else { - defaultMaterial = blenderContext.getDefaultMaterial().clone(); - defaultMaterial.getAdditionalRenderState().setFaceCullMode(FaceCullMode.Off); - } - - // getting or creating bevel object - List bevelObject = null; - Pointer pBevelObject = (Pointer) curveStructure.getFieldValue("bevobj"); - if (pBevelObject.isNotNull()) { - Pointer pBevelStructure = (Pointer) pBevelObject.fetchData().get(0).getFieldValue("data"); - Structure bevelStructure = pBevelStructure.fetchData().get(0); - bevelObject = this.toCurve(bevelStructure, blenderContext); - } else { - int bevResol = ((Number) curveStructure.getFieldValue("bevresol")).intValue(); - float extrude = ((Number) curveStructure.getFieldValue("ext1")).floatValue(); - float bevelDepth = ((Number) curveStructure.getFieldValue("ext2")).floatValue(); - if (bevelDepth > 0.0f) { - float handlerLength = bevelDepth / 2.0f; - - List conrtolPoints = new ArrayList(extrude > 0.0f ? 19 : 13); - if (extrude > 0.0f) { - 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(-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, 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)); + public CurvesTemporalMesh toCurve(Structure curveStructure, BlenderContext blenderContext) throws BlenderFileException { + CurvesTemporalMesh result = new CurvesTemporalMesh(curveStructure, blenderContext); - 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, 0, -extrude), new Vector3f(0, 0, extrude) }, 1, false); - Curve bevelCurve = new Curve(bevelSpline, bevResol); - bevelObject = new ArrayList(1); - bevelObject.add(new Geometry("", bevelCurve)); - } - } - - // getting taper object - Spline taperObject = null; - Pointer pTaperObject = (Pointer) curveStructure.getFieldValue("taperobj"); - if (bevelObject != null && pTaperObject.isNotNull()) { - Pointer pTaperStructure = (Pointer) pTaperObject.fetchData().get(0).getFieldValue("data"); - Structure taperStructure = pTaperStructure.fetchData().get(0); - taperObject = this.loadTaperObject(taperStructure); - } - - Vector3f loc = this.getLoc(curveStructure); - // creating the result curves - List result = new ArrayList(nurbs.size()); - for (Entry> nurbEntry : nurbs.entrySet()) { - for (Structure nurb : nurbEntry.getValue()) { - int type = ((Number) nurb.getFieldValue("type")).intValue(); - List nurbGeoms = null; - if ((type & 0x01) != 0) {// Bezier curve - nurbGeoms = this.loadBezierCurve(loc, nurb, bevelObject, taperObject, blenderContext); - } else if ((type & 0x04) != 0) {// NURBS - nurbGeoms = this.loadNurb(loc, nurb, bevelObject, taperObject, blenderContext); - } - if (nurbGeoms != null) {// setting the name and assigning materials - for (Geometry nurbGeom : nurbGeoms) { - if (materialContexts != null) { - materialContexts[nurbEntry.getKey().intValue()].applyMaterial(nurbGeom, curveStructure.getOldMemoryAddress(), null, blenderContext); - } else { - nurbGeom.setMaterial(defaultMaterial); - } - nurbGeom.setName(name); - result.add(nurbGeom); - } - } - } - } - - // reading custom properties - if (blenderContext.getBlenderKey().isLoadObjectProperties() && result.size() > 0) { - Properties properties = this.loadProperties(curveStructure, blenderContext); - // the loaded property is a group property, so we need to get each value and set it to every geometry of the curve - if (properties != null && properties.getValue() != null) { - for(Geometry geom : result) { - this.applyProperties(geom, properties); - } - } + if (blenderContext.getBlenderKey().isLoadObjectProperties()) { + LOGGER.fine("Reading custom properties."); + result.setProperties(this.loadProperties(curveStructure, blenderContext)); } return result; } - /** - * This method loads the bezier curve. - * @param loc - * the translation of the curve - * @param nurb - * the nurb structure - * @param bevelObject - * the bevel object - * @param taperObject - * the taper object - * @param blenderContext - * the blender context - * @return a list of geometries representing the curves - * @throws BlenderFileException - * an exception is thrown when there are problems with the blender file - */ - 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(); - if (pBezierTriple.isNotNull()) { - boolean smooth = (((Number) nurb.getFlatFieldValue("flag")).intValue() & 0x01) != 0; - int resolution = ((Number) nurb.getFieldValue("resolu")).intValue(); - boolean cyclic = (((Number) nurb.getFieldValue("flagu")).intValue() & 0x01) != 0; - - // creating the curve object - BezierCurve bezierCurve = new BezierCurve(0, pBezierTriple.fetchData(), 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, (float)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) { - controlPoints.add(controlPoints.get(i)); - } - } - // removing the first and last handles - controlPoints.remove(0); - controlPoints.remove(controlPoints.size() - 1); - - // creating curve - Spline spline = new Spline(SplineType.Bezier, controlPoints, 0, false); - Curve curve = new Curve(spline, resolution); - if (bevelObject == null) {// creating a normal curve - Geometry curveGeometry = new Geometry(null, curve); - 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); - } - } - return result; - } - - /** - * This method loads the NURBS curve or surface. - * @param loc - * object's location - * @param nurb - * the NURBS data structure - * @param bevelObject - * the bevel object to be applied - * @param taperObject - * the taper object to be applied - * @param blenderContext - * the blender context - * @return a list of geometries that represents the loaded NURBS curve or surface - * @throws BlenderFileException - * an exception is throw when problems with blender loaded data occurs - */ - @SuppressWarnings("unchecked") - protected List loadNurb(Vector3f loc, Structure nurb, List bevelObject, Spline taperObject, BlenderContext blenderContext) throws BlenderFileException { - // loading the knots - List[] knots = new List[2]; - Pointer[] pKnots = new Pointer[] { (Pointer) nurb.getFieldValue("knotsu"), (Pointer) nurb.getFieldValue("knotsv") }; - for (int i = 0; i < knots.length; ++i) { - if (pKnots[i].isNotNull()) { - FileBlockHeader fileBlockHeader = blenderContext.getFileBlock(pKnots[i].getOldMemoryAddress()); - BlenderInputStream blenderInputStream = blenderContext.getInputStream(); - blenderInputStream.setPosition(fileBlockHeader.getBlockPosition()); - int knotsAmount = fileBlockHeader.getCount() * fileBlockHeader.getSize() / 4; - knots[i] = new ArrayList(knotsAmount); - for (int j = 0; j < knotsAmount; ++j) { - knots[i].add(Float.valueOf(blenderInputStream.readFloat())); - } - } - } - - // loading the flags and orders (basis functions degrees) - int flagU = ((Number) nurb.getFieldValue("flagu")).intValue(); - int flagV = ((Number) nurb.getFieldValue("flagv")).intValue(); - int orderU = ((Number) nurb.getFieldValue("orderu")).intValue(); - int orderV = ((Number) nurb.getFieldValue("orderv")).intValue(); - - // loading control points and their weights - int pntsU = ((Number) nurb.getFieldValue("pntsu")).intValue(); - int pntsV = ((Number) nurb.getFieldValue("pntsv")).intValue(); - List bPoints = ((Pointer) nurb.getFieldValue("bp")).fetchData(); - List> controlPoints = new ArrayList>(pntsV); - for (int i = 0; i < pntsV; ++i) { - List uControlPoints = new ArrayList(pntsU); - for (int j = 0; j < pntsU; ++j) { - DynamicArray vec = (DynamicArray) bPoints.get(j + i * pntsU).getFieldValue("vec"); - if (fixUpAxis) { - uControlPoints.add(new Vector4f(vec.get(0).floatValue(), vec.get(2).floatValue(), -vec.get(1).floatValue(), vec.get(3).floatValue())); - } else { - uControlPoints.add(new Vector4f(vec.get(0).floatValue(), vec.get(1).floatValue(), vec.get(2).floatValue(), vec.get(3).floatValue())); - } - } - if ((flagU & 0x01) != 0) { - for (int k = 0; k < orderU - 1; ++k) { - uControlPoints.add(uControlPoints.get(k)); - } - } - controlPoints.add(uControlPoints); - } - if ((flagV & 0x01) != 0) { - for (int k = 0; k < orderV - 1; ++k) { - controlPoints.add(controlPoints.get(k)); - } - } - - int resolu = ((Number) nurb.getFieldValue("resolu")).intValue() + 1; - List result; - if (knots[1] == null) {// creating the curve - Spline nurbSpline = new Spline(controlPoints.get(0), knots[0]); - Curve nurbCurve = new Curve(nurbSpline, resolu); - if (bevelObject != null) { - result = this.applyBevelAndTaper(nurbCurve, bevelObject, taperObject, true, blenderContext);// TODO: smooth - } else { - result = new ArrayList(1); - Geometry nurbGeometry = new Geometry("", nurbCurve); - result.add(nurbGeometry); - } - } else {// creating the nurb surface - int resolv = ((Number) nurb.getFieldValue("resolv")).intValue() + 1; - Surface nurbSurface = Surface.createNurbsSurface(controlPoints, knots, resolu, resolv, orderU, orderV); - Geometry nurbGeometry = new Geometry("", nurbSurface); - result = new ArrayList(1); - result.add(nurbGeometry); - } - return result; - } - - /** - * 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) { - if (taper == null) { - return 1;// return scale = 1 if no taper is applied - } - 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. - * @param curve - * the curve we apply the objects to - * @param bevelObject - * the bevel object - * @param taperObject - * the taper object - * @param smooth - * the smooth flag - * @param blenderContext - * the blender context - * @return a list of geometries representing the beveled and/or tapered curve - */ - protected List applyBevelAndTaper(Curve curve, List bevelObject, Spline taperObject, boolean smooth, BlenderContext blenderContext) { - Vector3f[] curvePoints = BufferUtils.getVector3Array(curve.getFloatBuffer(Type.Position)); - Vector3f subtractResult = new Vector3f(); - float curveLength = curve.getLength(); - - FloatBuffer[] vertexBuffers = new FloatBuffer[bevelObject.size()]; - FloatBuffer[] normalBuffers = new FloatBuffer[bevelObject.size()]; - IndexBuffer[] indexBuffers = new IndexBuffer[bevelObject.size()]; - for (int geomIndex = 0; geomIndex < bevelObject.size(); ++geomIndex) { - 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); - - if (bevels.size() > 2) { - // changing the first and last bevel so that they are parallel to their neighbours (blender works this way) - // notice this implicates that the distances of every corresponding point in th two bevels must be identical and - // equal to the distance between the points on curve that define the bevel position - // so instead doing complicated rotations on each point we will simply properly translate each of them - - int[][] pointIndexes = new int[][] { { 0, 1 }, { curvePoints.length - 1, curvePoints.length - 2 } }; - for (int[] indexes : pointIndexes) { - float distance = curvePoints[indexes[1]].subtract(curvePoints[indexes[0]], subtractResult).length(); - Vector3f[] bevel = bevels.get(indexes[0]); - Vector3f[] nextBevel = bevels.get(indexes[1]); - for (int i = 0; i < bevel.length; ++i) { - float d = bevel[i].subtract(nextBevel[i], subtractResult).length(); - subtractResult.normalizeLocal().multLocal(distance - d); - bevel[i].addLocal(subtractResult); - } - } - } - - // apply scales to the bevels - float lengthAlongCurve = 0; - for (int i = 0; i < curvePoints.length; ++i) { - if (i > 0) { - lengthAlongCurve += curvePoints[i].subtract(curvePoints[i - 1], subtractResult).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]); - if (indexBuffers[i].getBuffer() instanceof IntBuffer) { - mesh.setBuffer(Type.Index, 3, (IntBuffer) indexBuffers[i].getBuffer()); - } else { - mesh.setBuffer(Type.Index, 3, (ShortBuffer) indexBuffers[i].getBuffer()); - } - 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.size(); 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); - } - - 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) { - int putIndex = 0; - if (smooth) { - int indexBufferSize = (bevelRepeats - 1) * (bevelShapeVertexCount - 1) * 6; - IndexBuffer result = IndexBuffer.createIndexBuffer(indexBufferSize, indexBufferSize); - - for (int i = 0; i < bevelRepeats - 1; ++i) { - for (int j = 0; j < bevelShapeVertexCount - 1; ++j) { - result.put(putIndex++, i * bevelShapeVertexCount + j); - result.put(putIndex++, i * bevelShapeVertexCount + j + 1); - result.put(putIndex++, (i + 1) * bevelShapeVertexCount + j); - - result.put(putIndex++, i * bevelShapeVertexCount + j + 1); - result.put(putIndex++, (i + 1) * bevelShapeVertexCount + j + 1); - result.put(putIndex++, (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 = IndexBuffer.createIndexBuffer(indexBufferSize, indexBufferSize); - for (int i = 0; i < indexBufferSize; ++i) { - result.put(putIndex++, i); - } - return result; - } - } - /** * The method transforms the bevel along the curve. * @@ -689,7 +92,7 @@ public class CurvesHelper extends AbstractBlenderHelper { * next curve point * @return points of transformed bevel */ - private Vector3f[] transformBevel(Vector3f[] bevel, Vector3f prevPos, Vector3f currPos, Vector3f nextPos) { + protected Vector3f[] transformBevel(Vector3f[] bevel, Vector3f prevPos, Vector3f currPos, Vector3f nextPos) { bevel = bevel.clone(); // currPos and directionVector define the line in 3D space @@ -736,103 +139,21 @@ public class CurvesHelper extends AbstractBlenderHelper { * the second curve's point * @return points of transformed bevel */ - private Vector3f[] transformToFirstLineOfBevelPoints(Vector3f[] startingLinePoints, Vector3f firstCurvePoint, Vector3f secondCurvePoint) { + protected Vector3f[] transformToFirstLineOfBevelPoints(Vector3f[] startingLinePoints, Vector3f firstCurvePoint, Vector3f secondCurvePoint) { Vector3f planeNormal = secondCurvePoint.subtract(firstCurvePoint).normalizeLocal(); - float angle = FastMath.acos(planeNormal.dot(Vector3f.UNIT_Y)); - planeNormal.crossLocal(Vector3f.UNIT_Y).normalizeLocal();// planeNormal is the rotation axis now - Quaternion pointRotation = new Quaternion(); - pointRotation.fromAngleAxis(angle, planeNormal); - + float angle = FastMath.acos(planeNormal.dot(Vector3f.UNIT_X)); + Vector3f rotationVector = Vector3f.UNIT_X.cross(planeNormal).normalizeLocal(); + Matrix4f m = new Matrix4f(); - m.setRotationQuaternion(pointRotation); + m.setRotationQuaternion(new Quaternion().fromAngleAxis(angle, rotationVector)); m.setTranslation(firstCurvePoint); - float[] temp = new float[] { 0, 0, 0, 1 }; + Vector3f temp = new Vector3f(); Vector3f[] verts = new Vector3f[startingLinePoints.length]; - for (int j = 0; j < verts.length; ++j) { - temp[0] = startingLinePoints[j].x; - temp[1] = startingLinePoints[j].y; - temp[2] = startingLinePoints[j].z; - temp = m.mult(temp);// the result is stored in the array - if (fixUpAxis) { - verts[j] = new Vector3f(temp[0], -temp[2], temp[1]); - } else { - verts[j] = new Vector3f(temp[0], temp[1], temp[2]); - } + for (int i = 0; i < verts.length; ++i) { + verts[i] = m.mult(startingLinePoints[i], temp).clone(); } return verts; } - - /** - * The method adds a normal to the given map. Depending in the smooth factor - * it is either merged with the revious normal or not. - * - * @param normalToAdd - * the normal vector to be added - * @param normalMap - * the normal map where we add vectors - * @param smooth - * the smooth flag - * @param indexes - * the indexes of the normals - */ - private void addNormal(Vector3f normalToAdd, Map normalMap, boolean smooth, int... indexes) { - for (int index : indexes) { - Vector3f n = normalMap.get(index); - if (!smooth || n == null) { - normalMap.put(index, normalToAdd.clone()); - } else { - n.addLocal(normalToAdd).normalizeLocal(); - } - } - } - - /** - * This method loads the taper object. - * - * @param taperStructure - * the taper structure - * @return the taper object - * @throws BlenderFileException - */ - protected Spline loadTaperObject(Structure taperStructure) throws BlenderFileException { - // reading nurbs - List nurbStructures = ((Structure) taperStructure.getFieldValue("nurb")).evaluateListBase(); - for (Structure nurb : nurbStructures) { - Pointer pBezierTriple = (Pointer) nurb.getFieldValue("bezt"); - if (pBezierTriple.isNotNull()) { - // creating the curve object - BezierCurve bezierCurve = new BezierCurve(0, pBezierTriple.fetchData(), 3); - List controlPoints = bezierCurve.getControlPoints(); - // removing the first and last handles - controlPoints.remove(0); - controlPoints.remove(controlPoints.size() - 1); - - // return the first taper curve that has more than 3 control points - if (controlPoints.size() > 3) { - 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 - */ - @SuppressWarnings("unchecked") - protected Vector3f getLoc(Structure curveStructure) { - DynamicArray locArray = (DynamicArray) curveStructure.getFieldValue("loc"); - if (fixUpAxis) { - return new Vector3f(locArray.get(0).floatValue(), locArray.get(1).floatValue(), -locArray.get(2).floatValue()); - } else { - return new Vector3f(locArray.get(0).floatValue(), locArray.get(2).floatValue(), locArray.get(1).floatValue()); - } - } } \ No newline at end of file diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/curves/CurvesTemporalMesh.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/curves/CurvesTemporalMesh.java new file mode 100644 index 000000000..41117bf85 --- /dev/null +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/curves/CurvesTemporalMesh.java @@ -0,0 +1,890 @@ +package com.jme3.scene.plugins.blender.curves; + +import java.nio.FloatBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +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.RenderState.FaceCullMode; +import com.jme3.math.FastMath; +import com.jme3.math.Spline; +import com.jme3.math.Spline.SplineType; +import com.jme3.math.Vector3f; +import com.jme3.math.Vector4f; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.scene.mesh.IndexBuffer; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.file.BlenderFileException; +import com.jme3.scene.plugins.blender.file.BlenderInputStream; +import com.jme3.scene.plugins.blender.file.DynamicArray; +import com.jme3.scene.plugins.blender.file.FileBlockHeader; +import com.jme3.scene.plugins.blender.file.Pointer; +import com.jme3.scene.plugins.blender.file.Structure; +import com.jme3.scene.plugins.blender.materials.MaterialContext; +import com.jme3.scene.plugins.blender.materials.MaterialHelper; +import com.jme3.scene.plugins.blender.meshes.Edge; +import com.jme3.scene.plugins.blender.meshes.Face; +import com.jme3.scene.plugins.blender.meshes.TemporalMesh; +import com.jme3.scene.shape.Curve; +import com.jme3.scene.shape.Surface; +import com.jme3.util.BufferUtils; + +/** + * A temporal mesh for curves and surfaces. It works in similar way as TemporalMesh for meshes. + * It prepares all neccessary lines and faces and allows to apply modifiers just like in regular temporal mesh. + * + * @author Marcin Roguski (Kaelthas) + */ +public class CurvesTemporalMesh extends TemporalMesh { + private static final Logger LOGGER = Logger.getLogger(CurvesTemporalMesh.class.getName()); + + private static final int TYPE_BEZIER = 0x0001; + private static final int TYPE_NURBS = 0x0004; + + private static final int FLAG_3D = 0x0001; + private static final int FLAG_FRONT = 0x0002; + private static final int FLAG_BACK = 0x0004; + private static final int FLAG_FILL_CAPS = 0x4000; + + private static final int FLAG_SMOOTH = 0x0001; + + protected CurvesHelper curvesHelper; + protected boolean is2D; + protected boolean isFront; + protected boolean isBack; + protected boolean fillCaps; + protected float bevelStart; + protected float bevelEnd; + protected List beziers = new ArrayList(); + protected CurvesTemporalMesh bevelObject; + protected CurvesTemporalMesh taperObject; + /** The scale that is used if the curve is a bevel or taper curve. */ + protected Vector3f scale = new Vector3f(1, 1, 1); + + /** + * The constructor creates an empty temporal mesh. + * @param blenderContext + * the blender context + * @throws BlenderFileException + * this will never be thrown here + */ + protected CurvesTemporalMesh(BlenderContext blenderContext) throws BlenderFileException { + super(null, blenderContext, false); + } + + /** + * Loads the temporal mesh from the given curve structure. The mesh can be either curve or surface. + * @param curveStructure + * the structure that contains the curve/surface data + * @param blenderContext + * the blender context + * @throws BlenderFileException + * an exception is thrown when problems with reading occur + */ + public CurvesTemporalMesh(Structure curveStructure, BlenderContext blenderContext) throws BlenderFileException { + this(curveStructure, new Vector3f(1, 1, 1), true, blenderContext); + } + + /** + * Loads the temporal mesh from the given curve structure. The mesh can be either curve or surface. + * @param curveStructure + * the structure that contains the curve/surface data + * @param scale + * the scale used if the current curve is used as a bevel curve + * @param loadBevelAndTaper indicates if bevel and taper should be loaded (this is not needed for curves that are loaded to be used as bevel and taper) + * @param blenderContext + * the blender context + * @throws BlenderFileException + * an exception is thrown when problems with reading occur + */ + @SuppressWarnings("unchecked") + private CurvesTemporalMesh(Structure curveStructure, Vector3f scale, boolean loadBevelAndTaper, BlenderContext blenderContext) throws BlenderFileException { + super(curveStructure, blenderContext, false); + name = curveStructure.getName(); + curvesHelper = blenderContext.getHelper(CurvesHelper.class); + this.scale = scale; + + int flag = ((Number) curveStructure.getFieldValue("flag")).intValue(); + is2D = (flag & FLAG_3D) == 0; + if (is2D) { + // TODO: add support for 3D flag + LOGGER.warning("2D flag not yet supported for curves!"); + } + isFront = (flag & FLAG_FRONT) != 0; + isBack = (flag & FLAG_BACK) != 0; + fillCaps = (flag & FLAG_FILL_CAPS) != 0; + bevelStart = ((Number) curveStructure.getFieldValue("bevfac1", 0)).floatValue(); + bevelEnd = ((Number) curveStructure.getFieldValue("bevfac2", 1)).floatValue(); + if (bevelStart > bevelEnd) { + float temp = bevelStart; + bevelStart = bevelEnd; + bevelEnd = temp; + } + + LOGGER.fine("Reading nurbs (and sorting them by material)."); + Map> nurbs = new HashMap>(); + List nurbStructures = ((Structure) curveStructure.getFieldValue("nurb")).evaluateListBase(); + for (Structure nurb : nurbStructures) { + Number matNumber = (Number) nurb.getFieldValue("mat_nr"); + List nurbList = nurbs.get(matNumber); + if (nurbList == null) { + nurbList = new ArrayList(); + nurbs.put(matNumber, nurbList); + } + nurbList.add(nurb); + } + + LOGGER.fine("Getting materials."); + MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class); + materials = materialHelper.getMaterials(curveStructure, blenderContext); + if (materials != null) { + for (MaterialContext materialContext : materials) { + materialContext.setFaceCullMode(FaceCullMode.Off); + } + } + + LOGGER.fine("Getting or creating bevel object."); + bevelObject = loadBevelAndTaper ? this.loadBevelObject(curveStructure) : null; + + LOGGER.fine("Getting taper object."); + Pointer pTaperObject = (Pointer) curveStructure.getFieldValue("taperobj"); + if (bevelObject != null && pTaperObject.isNotNull()) { + Structure taperObjectStructure = pTaperObject.fetchData().get(0); + DynamicArray scaleArray = (DynamicArray) taperObjectStructure.getFieldValue("size"); + scale = blenderContext.getBlenderKey().isFixUpAxis() ? new Vector3f(scaleArray.get(0).floatValue(), scaleArray.get(1).floatValue(), scaleArray.get(2).floatValue()) : new Vector3f(scaleArray.get(0).floatValue(), scaleArray.get(2).floatValue(), scaleArray.get(1).floatValue()); + Pointer pTaperStructure = (Pointer) taperObjectStructure.getFieldValue("data"); + Structure taperStructure = pTaperStructure.fetchData().get(0); + taperObject = new CurvesTemporalMesh(taperStructure, blenderContext); + } + + LOGGER.fine("Creating the result curves."); + for (Entry> nurbEntry : nurbs.entrySet()) { + for (Structure nurb : nurbEntry.getValue()) { + int type = ((Number) nurb.getFieldValue("type")).intValue(); + if ((type & TYPE_BEZIER) != 0) { + this.loadBezierCurve(nurb, nurbEntry.getKey().intValue()); + } else if ((type & TYPE_NURBS) != 0) { + this.loadNurbSurface(nurb, nurbEntry.getKey().intValue()); + } else { + throw new BlenderFileException("Unknown curve type: " + type); + } + } + } + + if (bevelObject != null && beziers.size() > 0) { + this.append(this.applyBevelAndTaper(this, bevelObject, taperObject, blenderContext)); + } else { + int originalVerticesAmount = vertices.size(); + for (BezierLine bezierLine : beziers) { + vertices.add(bezierLine.vertices[0]); + Vector3f v = bezierLine.vertices[1].subtract(bezierLine.vertices[0]).normalizeLocal(); + float temp = v.x; + v.x = -v.y; + v.y = temp; + v.z = 0; + normals.add(v);// this will be smoothed in the next iteration + + for (int i = 1; i < bezierLine.vertices.length; ++i) { + vertices.add(bezierLine.vertices[i]); + edges.add(new Edge(originalVerticesAmount + i - 1, originalVerticesAmount + i, 0, false, this)); + + // generating normal for vertex at 'i' + v = bezierLine.vertices[i].subtract(bezierLine.vertices[i - 1]).normalizeLocal(); + temp = v.x; + v.x = -v.y; + v.y = temp; + v.z = 0; + + // make the previous normal smooth + normals.get(i - 1).addLocal(v).multLocal(0.5f).normalizeLocal(); + normals.add(v);// this will be smoothed in the next iteration + } + } + } + } + + /** + * The method computes the value of a point at the certain relational distance from its beggining. + * @param alongRatio + * the relative distance along the curve; should be a value between 0 and 1 inclusive; + * if the value exceeds the boundaries it is truncated to them + * @return computed value along the curve + */ + private Vector3f getValueAlongCurve(float alongRatio) { + alongRatio = FastMath.clamp(alongRatio, 0, 1); + Vector3f result = new Vector3f(); + float probeLength = this.getLength() * alongRatio, length = 0; + for (BezierLine bezier : beziers) { + float edgeLength = bezier.getLength(); + if (length + edgeLength >= probeLength) { + float ratioAlongEdge = (probeLength - length) / edgeLength; + return bezier.getValueAlongCurve(ratioAlongEdge); + } + length += edgeLength; + } + return result; + } + + /** + * @return the length of the curve + */ + private float getLength() { + float result = 0; + for (BezierLine bezier : beziers) { + result += bezier.getLength(); + } + return result; + } + + /** + * The methods loads the bezier curve from the given structure. + * @param nurbStructure + * the structure containing a single curve definition + * @param materialIndex + * the index of this segment's material + * @throws BlenderFileException + * an exception is thrown when problems with reading occur + */ + private void loadBezierCurve(Structure nurbStructure, int materialIndex) throws BlenderFileException { + Pointer pBezierTriple = (Pointer) nurbStructure.getFieldValue("bezt"); + if (pBezierTriple.isNotNull()) { + int resolution = ((Number) nurbStructure.getFieldValue("resolu")).intValue(); + boolean cyclic = (((Number) nurbStructure.getFieldValue("flagu")).intValue() & 0x01) != 0; + boolean smooth = (((Number) nurbStructure.getFieldValue("flag")).intValue() & FLAG_SMOOTH) != 0; + + // creating the curve object + BezierCurve bezierCurve = new BezierCurve(0, pBezierTriple.fetchData(), 3, blenderContext.getBlenderKey().isFixUpAxis()); + List controlPoints = bezierCurve.getControlPoints(); + + if (cyclic) { + // copy the first three points at the end + for (int i = 0; i < 3; ++i) { + controlPoints.add(controlPoints.get(i)); + } + } + // removing the first and last handles + controlPoints.remove(0); + controlPoints.remove(controlPoints.size() - 1); + + // creating curve + Curve curve = new Curve(new Spline(SplineType.Bezier, controlPoints, 0, false), resolution); + + FloatBuffer vertsBuffer = (FloatBuffer) curve.getBuffer(Type.Position).getData(); + beziers.add(new BezierLine(BufferUtils.getVector3Array(vertsBuffer), materialIndex, smooth, cyclic)); + } + } + + /** + * This method loads the NURBS curve or surface. + * @param nurb + * the NURBS data structure + * @throws BlenderFileException + * an exception is thrown when problems with reading occur + */ + @SuppressWarnings("unchecked") + private void loadNurbSurface(Structure nurb, int materialIndex) throws BlenderFileException { + // loading the knots + List[] knots = new List[2]; + Pointer[] pKnots = new Pointer[] { (Pointer) nurb.getFieldValue("knotsu"), (Pointer) nurb.getFieldValue("knotsv") }; + for (int i = 0; i < knots.length; ++i) { + if (pKnots[i].isNotNull()) { + FileBlockHeader fileBlockHeader = blenderContext.getFileBlock(pKnots[i].getOldMemoryAddress()); + BlenderInputStream blenderInputStream = blenderContext.getInputStream(); + blenderInputStream.setPosition(fileBlockHeader.getBlockPosition()); + int knotsAmount = fileBlockHeader.getCount() * fileBlockHeader.getSize() / 4; + knots[i] = new ArrayList(knotsAmount); + for (int j = 0; j < knotsAmount; ++j) { + knots[i].add(Float.valueOf(blenderInputStream.readFloat())); + } + } + } + + // loading the flags and orders (basis functions degrees) + int flag = ((Number) nurb.getFieldValue("flag")).intValue(); + boolean smooth = (flag & FLAG_SMOOTH) != 0; + int flagU = ((Number) nurb.getFieldValue("flagu")).intValue(); + int flagV = ((Number) nurb.getFieldValue("flagv")).intValue(); + int orderU = ((Number) nurb.getFieldValue("orderu")).intValue(); + int orderV = ((Number) nurb.getFieldValue("orderv")).intValue(); + + // loading control points and their weights + int pntsU = ((Number) nurb.getFieldValue("pntsu")).intValue(); + int pntsV = ((Number) nurb.getFieldValue("pntsv")).intValue(); + List bPoints = ((Pointer) nurb.getFieldValue("bp")).fetchData(); + List> controlPoints = new ArrayList>(pntsV); + for (int i = 0; i < pntsV; ++i) { + List uControlPoints = new ArrayList(pntsU); + for (int j = 0; j < pntsU; ++j) { + DynamicArray vec = (DynamicArray) bPoints.get(j + i * pntsU).getFieldValue("vec"); + if (blenderContext.getBlenderKey().isFixUpAxis()) { + uControlPoints.add(new Vector4f(vec.get(0).floatValue(), vec.get(2).floatValue(), -vec.get(1).floatValue(), vec.get(3).floatValue())); + } else { + uControlPoints.add(new Vector4f(vec.get(0).floatValue(), vec.get(1).floatValue(), vec.get(2).floatValue(), vec.get(3).floatValue())); + } + } + if ((flagU & 0x01) != 0) { + for (int k = 0; k < orderU - 1; ++k) { + uControlPoints.add(uControlPoints.get(k)); + } + } + controlPoints.add(uControlPoints); + } + if ((flagV & 0x01) != 0) { + for (int k = 0; k < orderV - 1; ++k) { + controlPoints.add(controlPoints.get(k)); + } + } + + int originalVerticesAmount = vertices.size(); + int resolu = ((Number) nurb.getFieldValue("resolu")).intValue(); + if (knots[1] == null) {// creating the NURB curve + Curve curve = new Curve(new Spline(controlPoints.get(0), knots[0]), resolu); + FloatBuffer vertsBuffer = (FloatBuffer) curve.getBuffer(Type.Position).getData(); + beziers.add(new BezierLine(BufferUtils.getVector3Array(vertsBuffer), materialIndex, smooth, false)); + } else {// creating the NURB surface + int resolv = ((Number) nurb.getFieldValue("resolv")).intValue(); + int uSegments = resolu * controlPoints.get(0).size() - 1; + int vSegments = resolv * controlPoints.size() - 1; + Surface nurbSurface = Surface.createNurbsSurface(controlPoints, knots, uSegments, vSegments, orderU, orderV, smooth); + + FloatBuffer vertsBuffer = (FloatBuffer) nurbSurface.getBuffer(Type.Position).getData(); + vertices.addAll(Arrays.asList(BufferUtils.getVector3Array(vertsBuffer))); + FloatBuffer normalsBuffer = (FloatBuffer) nurbSurface.getBuffer(Type.Normal).getData(); + normals.addAll(Arrays.asList(BufferUtils.getVector3Array(normalsBuffer))); + + IndexBuffer indexBuffer = nurbSurface.getIndexBuffer(); + for (int i = 0; i < indexBuffer.size(); i += 3) { + int index1 = indexBuffer.get(i) + originalVerticesAmount; + int index2 = indexBuffer.get(i + 1) + originalVerticesAmount; + int index3 = indexBuffer.get(i + 2) + originalVerticesAmount; + faces.add(new Face(new Integer[] { index1, index2, index3 }, smooth, materialIndex, null, null, this)); + } + } + } + + /** + * The method loads the bevel object that sould be applied to curve. It can either be another curve or a generated one + * based on the bevel generating parameters in blender. + * @param curveStructure + * the structure with the curve's data (the curve being loaded, NOT the bevel curve) + * @return the curve's bevel object + * @throws BlenderFileException + * an exception is thrown when problems with reading occur + */ + @SuppressWarnings("unchecked") + private CurvesTemporalMesh loadBevelObject(Structure curveStructure) throws BlenderFileException { + CurvesTemporalMesh bevelObject = null; + Pointer pBevelObject = (Pointer) curveStructure.getFieldValue("bevobj"); + boolean cyclic = false; + if (pBevelObject.isNotNull()) { + Structure bevelObjectStructure = pBevelObject.fetchData().get(0); + DynamicArray scaleArray = (DynamicArray) bevelObjectStructure.getFieldValue("size"); + Vector3f scale = blenderContext.getBlenderKey().isFixUpAxis() ? new Vector3f(scaleArray.get(0).floatValue(), scaleArray.get(1).floatValue(), scaleArray.get(2).floatValue()) : new Vector3f(scaleArray.get(0).floatValue(), scaleArray.get(2).floatValue(), scaleArray.get(1).floatValue()); + Pointer pBevelStructure = (Pointer) bevelObjectStructure.getFieldValue("data"); + Structure bevelStructure = pBevelStructure.fetchData().get(0); + bevelObject = new CurvesTemporalMesh(bevelStructure, scale, false, blenderContext); + + // transforming the bezier lines from plane XZ to plane YZ + for (BezierLine bl : bevelObject.beziers) { + for (Vector3f v : bl.vertices) { + // casting the bezier curve orthogonally on the plane XZ (making Y = 0) and then moving the plane XZ to ZY in a way that: + // -Z => +Y and +X => +Z and +Y => +X (but because casting would make Y = 0, then we simply set X = 0) + v.y = -v.z; + v.z = v.x; + v.x = 0; + } + + // bevel curves should not have repeated the first vertex at the end when they are cyclic (this is handled differently) + if (bl.isCyclic()) { + bl.removeLastVertex(); + } + } + } else { + fillCaps = false;// this option is inactive in blender when there is no bevel object applied + int bevResol = ((Number) curveStructure.getFieldValue("bevresol")).intValue(); + float extrude = ((Number) curveStructure.getFieldValue("ext1")).floatValue(); + float bevelDepth = ((Number) curveStructure.getFieldValue("ext2")).floatValue(); + float offset = ((Number) curveStructure.getFieldValue("offset", 0)).floatValue(); + if (offset != 0) { + // TODO: add support for offset parameter + LOGGER.warning("Offset parameter not yet supported."); + } + Curve bevelCurve = null; + if (bevelDepth > 0.0f) { + float handlerLength = bevelDepth / 2.0f; + cyclic = !isFront && !isBack; + List conrtolPoints = new ArrayList(); + + // blenders from 2.49 to 2.52 did not pay attention to fron and back faces + // so in order to draw the scene exactly as it is in different blender versions the blender version is checked here + // when neither fron and back face is selected all version behave the same and draw full bevel around the curve + if (cyclic || blenderContext.getBlenderVersion() < 253) { + conrtolPoints.add(new Vector3f(0, -extrude - bevelDepth, 0)); + conrtolPoints.add(new Vector3f(0, -extrude - bevelDepth, -handlerLength)); + + conrtolPoints.add(new Vector3f(0, -extrude - handlerLength, -bevelDepth)); + conrtolPoints.add(new Vector3f(0, -extrude, -bevelDepth)); + conrtolPoints.add(new Vector3f(0, -extrude + handlerLength, -bevelDepth)); + + if (extrude > 0) { + conrtolPoints.add(new Vector3f(0, extrude - handlerLength, -bevelDepth)); + conrtolPoints.add(new Vector3f(0, extrude, -bevelDepth)); + conrtolPoints.add(new Vector3f(0, extrude + handlerLength, -bevelDepth)); + } + + conrtolPoints.add(new Vector3f(0, extrude + bevelDepth, -handlerLength)); + conrtolPoints.add(new Vector3f(0, extrude + bevelDepth, 0)); + + if (cyclic) { + conrtolPoints.add(new Vector3f(0, extrude + bevelDepth, handlerLength)); + + conrtolPoints.add(new Vector3f(0, extrude + handlerLength, bevelDepth)); + conrtolPoints.add(new Vector3f(0, extrude, bevelDepth)); + conrtolPoints.add(new Vector3f(0, extrude - handlerLength, bevelDepth)); + + if (extrude > 0) { + conrtolPoints.add(new Vector3f(0, -extrude + handlerLength, bevelDepth)); + conrtolPoints.add(new Vector3f(0, -extrude, bevelDepth)); + conrtolPoints.add(new Vector3f(0, -extrude - handlerLength, bevelDepth)); + } + + conrtolPoints.add(new Vector3f(0, -extrude - bevelDepth, handlerLength)); + conrtolPoints.add(new Vector3f(0, -extrude - bevelDepth, 0)); + } + } else { + if (extrude > 0) { + if (isBack) { + conrtolPoints.add(new Vector3f(0, -extrude - bevelDepth, 0)); + conrtolPoints.add(new Vector3f(0, -extrude - bevelDepth, -handlerLength)); + + conrtolPoints.add(new Vector3f(0, -extrude - handlerLength, -bevelDepth)); + } + + conrtolPoints.add(new Vector3f(0, -extrude, -bevelDepth)); + conrtolPoints.add(new Vector3f(0, -extrude + handlerLength, -bevelDepth)); + conrtolPoints.add(new Vector3f(0, extrude - handlerLength, -bevelDepth)); + conrtolPoints.add(new Vector3f(0, extrude, -bevelDepth)); + + if (isFront) { + conrtolPoints.add(new Vector3f(0, extrude + handlerLength, -bevelDepth)); + + conrtolPoints.add(new Vector3f(0, extrude + bevelDepth, -handlerLength)); + conrtolPoints.add(new Vector3f(0, extrude + bevelDepth, 0)); + } + } else { + if (isFront && isBack) { + conrtolPoints.add(new Vector3f(0, -bevelDepth, 0)); + conrtolPoints.add(new Vector3f(0, -bevelDepth, -handlerLength)); + + conrtolPoints.add(new Vector3f(0, -handlerLength, -bevelDepth)); + conrtolPoints.add(new Vector3f(0, 0, -bevelDepth)); + conrtolPoints.add(new Vector3f(0, handlerLength, -bevelDepth)); + + conrtolPoints.add(new Vector3f(0, bevelDepth, -handlerLength)); + conrtolPoints.add(new Vector3f(0, bevelDepth, 0)); + } else { + if (isBack) { + conrtolPoints.add(new Vector3f(0, -bevelDepth, 0)); + conrtolPoints.add(new Vector3f(0, -bevelDepth, -handlerLength)); + + conrtolPoints.add(new Vector3f(0, -handlerLength, -bevelDepth)); + conrtolPoints.add(new Vector3f(0, 0, -bevelDepth)); + } else { + conrtolPoints.add(new Vector3f(0, 0, -bevelDepth)); + conrtolPoints.add(new Vector3f(0, handlerLength, -bevelDepth)); + + conrtolPoints.add(new Vector3f(0, bevelDepth, -handlerLength)); + conrtolPoints.add(new Vector3f(0, bevelDepth, 0)); + } + } + } + } + + bevelCurve = new Curve(new Spline(SplineType.Bezier, conrtolPoints, 0, false), bevResol); + } else if (extrude > 0.0f) { + Spline bevelSpline = new Spline(SplineType.Linear, new Vector3f[] { new Vector3f(0, extrude, 0), new Vector3f(0, -extrude, 0) }, 1, false); + bevelCurve = new Curve(bevelSpline, bevResol); + } + if (bevelCurve != null) { + bevelObject = new CurvesTemporalMesh(blenderContext); + FloatBuffer vertsBuffer = (FloatBuffer) bevelCurve.getBuffer(Type.Position).getData(); + Vector3f[] verts = BufferUtils.getVector3Array(vertsBuffer); + if (cyclic) {// get rid of the last vertex which is identical to the first one + verts = Arrays.copyOf(verts, verts.length - 1); + } + bevelObject.beziers.add(new BezierLine(verts, 0, false, cyclic)); + } + } + return bevelObject; + } + + private List getScaledBeziers() { + if (scale.equals(Vector3f.UNIT_XYZ)) { + return beziers; + } + List result = new ArrayList(); + for (BezierLine bezierLine : beziers) { + result.add(bezierLine.scale(scale)); + } + return result; + } + + /** + * This method applies bevel and taper objects to the curve. + * @param curve + * the curve we apply the objects to + * @param bevelObject + * the bevel object + * @param taperObject + * the taper object + * @param blenderContext + * the blender context + * @return a list of geometries representing the beveled and/or tapered curve + * @throws BlenderFileException + * an exception is thrown when problems with reading occur + */ + private CurvesTemporalMesh applyBevelAndTaper(CurvesTemporalMesh curve, CurvesTemporalMesh bevelObject, CurvesTemporalMesh taperObject, BlenderContext blenderContext) throws BlenderFileException { + List bevelBezierLines = bevelObject.getScaledBeziers(); + List curveLines = curve.beziers; + if (bevelBezierLines.size() == 0 || curveLines.size() == 0) { + return null; + } + + CurvesTemporalMesh result = new CurvesTemporalMesh(blenderContext); + for (BezierLine curveLine : curveLines) { + Vector3f[] curveLineVertices = curveLine.getVertices(bevelStart, bevelEnd); + + for (BezierLine bevelBezierLine : bevelBezierLines) { + CurvesTemporalMesh partResult = new CurvesTemporalMesh(blenderContext); + + Vector3f[] bevelLineVertices = bevelBezierLine.getVertices(); + List bevels = new ArrayList(); + + Vector3f[] bevelPoints = curvesHelper.transformToFirstLineOfBevelPoints(bevelLineVertices, curveLineVertices[0], curveLineVertices[1]); + bevels.add(bevelPoints); + for (int i = 1; i < curveLineVertices.length - 1; ++i) { + bevelPoints = curvesHelper.transformBevel(bevelPoints, curveLineVertices[i - 1], curveLineVertices[i], curveLineVertices[i + 1]); + bevels.add(bevelPoints); + } + bevelPoints = curvesHelper.transformBevel(bevelPoints, curveLineVertices[curveLineVertices.length - 2], curveLineVertices[curveLineVertices.length - 1], null); + bevels.add(bevelPoints); + + Vector3f subtractResult = new Vector3f(); + if (bevels.size() > 2) { + // changing the first and last bevel so that they are parallel to their neighbours (blender works this way) + // notice this implicates that the distances of every corresponding point in the two bevels must be identical and + // equal to the distance between the points on curve that define the bevel position + // so instead doing complicated rotations on each point we will simply properly translate each of them + int[][] pointIndexes = new int[][] { { 0, 1 }, { curveLineVertices.length - 1, curveLineVertices.length - 2 } }; + for (int[] indexes : pointIndexes) { + float distance = curveLineVertices[indexes[1]].subtract(curveLineVertices[indexes[0]], subtractResult).length(); + Vector3f[] bevel = bevels.get(indexes[0]); + Vector3f[] nextBevel = bevels.get(indexes[1]); + for (int i = 0; i < bevel.length; ++i) { + float d = bevel[i].subtract(nextBevel[i], subtractResult).length(); + subtractResult.normalizeLocal().multLocal(distance - d); + bevel[i].addLocal(subtractResult); + } + } + } + + if (taperObject != null) { + float curveLength = curveLine.getLength(), lengthAlongCurve = bevelStart; + for (int i = 0; i < curveLineVertices.length; ++i) { + if (i > 0) { + lengthAlongCurve += curveLineVertices[i].subtract(curveLineVertices[i - 1], subtractResult).length(); + } + float taperScale = -taperObject.getValueAlongCurve(lengthAlongCurve / curveLength).z * taperObject.scale.z; + if (taperScale != 1) { + this.applyScale(bevels.get(i), curveLineVertices[i], taperScale); + } + } + } + + // adding vertices to the part result + for (Vector3f[] bevel : bevels) { + for (Vector3f d : bevel) { + partResult.getVertices().add(d); + } + } + + // preparing faces for the part result (each face is a quad) + int bevelVertCount = bevelPoints.length; + for (int i = 0; i < bevels.size() - 1; ++i) { + for (int j = 0; j < bevelVertCount - 1; ++j) { + Integer[] indexes = new Integer[] { i * bevelVertCount + j + 1, (i + 1) * bevelVertCount + j + 1, (i + 1) * bevelVertCount + j, i * bevelVertCount + j }; + partResult.getFaces().add(new Face(indexes, curveLine.isSmooth(), curveLine.getMaterialNumber(), null, null, partResult)); + partResult.getEdges().add(new Edge(indexes[0], indexes[1], 0, true, partResult)); + partResult.getEdges().add(new Edge(indexes[1], indexes[2], 0, true, partResult)); + partResult.getEdges().add(new Edge(indexes[2], indexes[3], 0, true, partResult)); + partResult.getEdges().add(new Edge(indexes[3], indexes[0], 0, true, partResult)); + } + if (bevelBezierLine.isCyclic()) { + int j = bevelVertCount - 1; + Integer[] indexes = new Integer[] { i * bevelVertCount, (i + 1) * bevelVertCount, (i + 1) * bevelVertCount + j, i * bevelVertCount + j }; + partResult.getFaces().add(new Face(indexes, curveLine.isSmooth(), curveLine.getMaterialNumber(), null, null, partResult)); + partResult.getEdges().add(new Edge(indexes[0], indexes[1], 0, true, partResult)); + partResult.getEdges().add(new Edge(indexes[1], indexes[2], 0, true, partResult)); + partResult.getEdges().add(new Edge(indexes[2], indexes[3], 0, true, partResult)); + partResult.getEdges().add(new Edge(indexes[3], indexes[0], 0, true, partResult)); + } + } + + partResult.generateNormals(); + + if (fillCaps) {// caps in blender behave as if they weren't affected by the smooth factor + // START CAP + Vector3f[] cap = bevels.get(0); + List capIndexes = new ArrayList(cap.length); + Vector3f capNormal = curveLineVertices[0].subtract(curveLineVertices[1]).normalizeLocal(); + for (int i = 0; i < cap.length; ++i) { + capIndexes.add(partResult.getVertices().size()); + partResult.getVertices().add(cap[i]); + partResult.getNormals().add(capNormal); + } + Collections.reverse(capIndexes);// the indexes ned to be reversed for the face to have fron face outside the beveled line + partResult.getFaces().add(new Face(capIndexes.toArray(new Integer[capIndexes.size()]), false, curveLine.getMaterialNumber(), null, null, partResult)); + for (int i = 1; i < capIndexes.size(); ++i) { + partResult.getEdges().add(new Edge(capIndexes.get(i - 1), capIndexes.get(i), 0, true, partResult)); + } + + // END CAP + cap = bevels.get(bevels.size() - 1); + capIndexes.clear(); + capNormal = curveLineVertices[curveLineVertices.length - 1].subtract(curveLineVertices[curveLineVertices.length - 2]).normalizeLocal(); + for (int i = 0; i < cap.length; ++i) { + capIndexes.add(partResult.getVertices().size()); + partResult.getVertices().add(cap[i]); + partResult.getNormals().add(capNormal); + } + partResult.getFaces().add(new Face(capIndexes.toArray(new Integer[capIndexes.size()]), false, curveLine.getMaterialNumber(), null, null, partResult)); + for (int i = 1; i < capIndexes.size(); ++i) { + partResult.getEdges().add(new Edge(capIndexes.get(i - 1), capIndexes.get(i), 0, true, partResult)); + } + } + + result.append(partResult); + } + } + + return result; + } + + /** + * The method generates normals for the curve. If any normals were already stored they are discarded. + */ + private void generateNormals() { + Map normalMap = new TreeMap(); + for (Face face : faces) { + // the first 3 verts are enough here (all faces are triangles except for the caps, but those are fully flat anyway) + int index1 = face.getIndexes().get(0); + int index2 = face.getIndexes().get(1); + int index3 = face.getIndexes().get(2); + + Vector3f n = FastMath.computeNormal(vertices.get(index1), vertices.get(index2), vertices.get(index3)); + for (int index : face.getIndexes()) { + Vector3f normal = normalMap.get(index); + if (normal == null) { + normalMap.put(index, n.clone()); + } else { + normal.addLocal(n).normalizeLocal(); + } + } + } + + normals.clear(); + Collections.addAll(normals, new Vector3f[normalMap.size()]); + for (Entry entry : normalMap.entrySet()) { + normals.set(entry.getKey(), entry.getValue()); + } + } + + /** + * 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); + } + } + + /** + * A helper class that represents a single bezier line. It consists of Edge's and allows to + * get a subline of a lentgh of the line. + * + * @author Marcin Roguski (Kaelthas) + */ + public static class BezierLine { + /** The edges of the bezier line. */ + private Vector3f[] vertices; + /** The material number of the line. */ + private int materialNumber; + /** Indicates if the line is smooth of flat. */ + private boolean smooth; + /** The length of the line. */ + private float length; + /** Indicates if the current line is cyclic or not. */ + private boolean cyclic; + + public BezierLine(Vector3f[] vertices, int materialNumber, boolean smooth, boolean cyclik) { + this.vertices = vertices; + this.materialNumber = materialNumber; + this.smooth = smooth; + cyclic = cyclik; + this.recomputeLength(); + } + + public BezierLine scale(Vector3f scale) { + BezierLine result = new BezierLine(vertices, materialNumber, smooth, cyclic); + result.vertices = new Vector3f[vertices.length]; + for (int i = 0; i < vertices.length; ++i) { + result.vertices[i] = vertices[i].mult(scale); + } + result.recomputeLength(); + return result; + } + + public void removeLastVertex() { + Vector3f[] newVertices = new Vector3f[vertices.length - 1]; + for (int i = 0; i < vertices.length - 1; ++i) { + newVertices[i] = vertices[i]; + } + vertices = newVertices; + this.recomputeLength(); + } + + private void recomputeLength() { + length = 0; + for (int i = 1; i < vertices.length; ++i) { + length += vertices[i - 1].distance(vertices[i]); + } + if (cyclic) { + // if the first vertex is repeated at the end the distance will be = 0 so it won't affect the result, and if it is not repeated + // then it is neccessary to add the length between the last and the first vertex + length += vertices[vertices.length - 1].distance(vertices[0]); + } + } + + public Vector3f[] getVertices() { + return this.getVertices(0, 1); + } + + public Vector3f[] getVertices(float startSlice, float endSlice) { + if (startSlice == 0 && endSlice == 1) { + return vertices; + } + List result = new ArrayList(); + float length = this.getLength(), temp = 0; + float startSliceLength = length * startSlice; + float endSliceLength = length * endSlice; + int index = 1; + + if (startSlice > 0) { + while (temp < startSliceLength) { + Vector3f v1 = vertices[index - 1]; + Vector3f v2 = vertices[index++]; + float edgeLength = v1.distance(v2); + temp += edgeLength; + if (temp == startSliceLength) { + result.add(v2); + } else if (temp > startSliceLength) { + result.add(v1.subtract(v2).normalizeLocal().multLocal(temp - startSliceLength).addLocal(v2)); + } + } + } + + if (endSlice < 1) { + if (index == vertices.length) { + Vector3f v1 = vertices[vertices.length - 2]; + Vector3f v2 = vertices[vertices.length - 1]; + result.add(v1.subtract(v2).normalizeLocal().multLocal(length - endSliceLength).addLocal(v2)); + } else { + for (int i = index; i < vertices.length && temp < endSliceLength; ++i) { + Vector3f v1 = vertices[index - 1]; + Vector3f v2 = vertices[index++]; + temp += v1.distance(v2); + if (temp == endSliceLength) { + result.add(v2); + } else if (temp > endSliceLength) { + result.add(v1.subtract(v2).normalizeLocal().multLocal(temp - startSliceLength).addLocal(v2)); + } + } + } + } else { + result.addAll(Arrays.asList(Arrays.copyOfRange(vertices, index, vertices.length))); + } + + return result.toArray(new Vector3f[result.size()]); + } + + /** + * The method computes the value of a point at the certain relational distance from its beggining. + * @param alongRatio + * the relative distance along the curve; should be a value between 0 and 1 inclusive; + * if the value exceeds the boundaries it is truncated to them + * @return computed value along the curve + */ + public Vector3f getValueAlongCurve(float alongRatio) { + alongRatio = FastMath.clamp(alongRatio, 0, 1); + Vector3f result = new Vector3f(); + float probeLength = this.getLength() * alongRatio; + float length = 0; + for (int i = 1; i < vertices.length; ++i) { + float edgeLength = vertices[i].distance(vertices[i - 1]); + if (length + edgeLength > probeLength) { + float ratioAlongEdge = (probeLength - length) / edgeLength; + return FastMath.interpolateLinear(ratioAlongEdge, vertices[i - 1], vertices[i]); + } else if (length + edgeLength == probeLength) { + return vertices[i]; + } + length += edgeLength; + } + + return result; + } + + /** + * @return the material number of this bezier line + */ + public int getMaterialNumber() { + return materialNumber; + } + + /** + * @return indicates if the line is smooth of flat + */ + public boolean isSmooth() { + return smooth; + } + + /** + * @return the length of this bezier line + */ + public float getLength() { + return length; + } + + /** + * @return indicates if the current line is cyclic or not + */ + public boolean isCyclic() { + return cyclic; + } + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/Structure.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/Structure.java index af8c17f5b..4f0de7e04 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/Structure.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/Structure.java @@ -126,12 +126,24 @@ public class Structure implements Cloneable { * @return the value of the field or null if no field with a given name is found */ public Object getFieldValue(String fieldName) { + return this.getFieldValue(fieldName, null); + } + + /** + * This method returns the value of the filed with a given name. + * @param fieldName + * the name of the field + * @param defaultValue + * the value that is being returned when no field of a given name is found + * @return the value of the field or the given default value if no field with a given name is found + */ + public Object getFieldValue(String fieldName, Object defaultValue) { for (Field field : fields) { if (field.name.equalsIgnoreCase(fieldName)) { return field.value; } } - return null; + return defaultValue; } /** diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Edge.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Edge.java index 4ee552d20..2291b5e56 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Edge.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Edge.java @@ -70,6 +70,20 @@ public class Edge { return index2; } + /** + * @return the first vertex of the edge + */ + public Vector3f getFirstVertex() { + return temporalMesh.getVertices().get(index1); + } + + /** + * @return the second vertex of the edge + */ + public Vector3f getSecondVertex() { + return temporalMesh.getVertices().get(index2); + } + /** * Returns the index other than the given. * @param index @@ -100,12 +114,25 @@ public class Edge { return inFace; } + /** + * @return the length of the edge + */ + public float getLength() { + return this.getFirstVertex().distance(this.getSecondVertex()); + } + + /** + * @return the mesh this edge belongs to + */ + public TemporalMesh getTemporalMesh() { + return temporalMesh; + } + /** * @return the centroid of the edge */ public Vector3f computeCentroid() { - List vertices = temporalMesh.getVertices(); - return vertices.get(index1).add(vertices.get(index2)).divideLocal(2); + return this.getFirstVertex().add(this.getSecondVertex()).divideLocal(2); } /** @@ -161,10 +188,10 @@ public class Edge { * @return true if the edges cross and false otherwise */ public boolean cross(Edge edge) { - 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); + Vector3f P1 = this.getFirstVertex(); + Vector3f P2 = edge.getFirstVertex(); + Vector3f u = this.getSecondVertex().subtract(P1); + Vector3f v = edge.getSecondVertex().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)); @@ -187,7 +214,7 @@ public class Edge { @Override public String toString() { String result = "Edge [" + index1 + ", " + index2 + "] {" + crease + "}"; - result += " (" + temporalMesh.getVertices().get(index1) + " -> " + temporalMesh.getVertices().get(index2) + ")"; + result += " (" + this.getFirstVertex() + " -> " + this.getSecondVertex() + ")"; if (inFace) { result += "[F]"; } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/TemporalMesh.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/TemporalMesh.java index e11ee706a..d4ef0d1bc 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/TemporalMesh.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/TemporalMesh.java @@ -17,6 +17,9 @@ import java.util.logging.Logger; import com.jme3.bounding.BoundingBox; import com.jme3.bounding.BoundingVolume; +import com.jme3.material.Material; +import com.jme3.material.RenderState.FaceCullMode; +import com.jme3.math.FastMath; import com.jme3.math.Vector2f; import com.jme3.math.Vector3f; import com.jme3.scene.Geometry; @@ -364,32 +367,34 @@ public class TemporalMesh extends Geometry { * the mesh to be appended */ public void append(TemporalMesh mesh) { - // we need to shift the indexes in faces, lines and points - int shift = vertices.size(); - if (shift > 0) { - for (Face face : mesh.faces) { - face.getIndexes().shiftIndexes(shift, null); - face.setTemporalMesh(this); - } - for (Edge edge : mesh.edges) { - edge.shiftIndexes(shift, null); - } - for (Point point : mesh.points) { - point.shiftIndexes(shift, null); + if (mesh != null) { + // we need to shift the indexes in faces, lines and points + int shift = vertices.size(); + if (shift > 0) { + for (Face face : mesh.faces) { + face.getIndexes().shiftIndexes(shift, null); + face.setTemporalMesh(this); + } + for (Edge edge : mesh.edges) { + edge.shiftIndexes(shift, null); + } + for (Point point : mesh.points) { + point.shiftIndexes(shift, null); + } } - } - faces.addAll(mesh.faces); - edges.addAll(mesh.edges); - points.addAll(mesh.points); + faces.addAll(mesh.faces); + edges.addAll(mesh.edges); + points.addAll(mesh.points); - vertices.addAll(mesh.vertices); - normals.addAll(mesh.normals); - vertexGroups.addAll(mesh.vertexGroups); - verticesColors.addAll(mesh.verticesColors); - boneIndexes.putAll(mesh.boneIndexes); + vertices.addAll(mesh.vertices); + normals.addAll(mesh.normals); + vertexGroups.addAll(mesh.vertexGroups); + verticesColors.addAll(mesh.verticesColors); + boneIndexes.putAll(mesh.boneIndexes); - this.rebuildIndexesMappings(); + this.rebuildIndexesMappings(); + } } /** @@ -506,11 +511,17 @@ public class TemporalMesh extends Geometry { for (List indexes : triangulatedIndexes) { assert indexes.size() == 3 : "The mesh has not been properly triangulated!"; + + Vector3f normal = null; + if(!face.isSmooth()) { + normal = FastMath.computeNormal(vertices.get(indexes.get(0)), vertices.get(indexes.get(1)), vertices.get(indexes.get(2))); + } + boneBuffers.clear(); for (int i = 0; i < 3; ++i) { int vertIndex = indexes.get(i); tempVerts[i] = vertices.get(vertIndex); - tempNormals[i] = normals.get(vertIndex); + tempNormals[i] = normal != null ? normal : normals.get(vertIndex); tempVertColors[i] = vertexColors != null ? vertexColors.get(face.getIndexes().indexOf(vertIndex)) : null; if (boneIndexes.size() > 0 && vertexGroups.size() > 0) { @@ -519,12 +530,12 @@ public class TemporalMesh extends Geometry { for (Entry entry : boneIndexes.entrySet()) { if (vertexGroupsForVertex.containsKey(entry.getKey())) { float weight = vertexGroupsForVertex.get(entry.getKey()); - if(weight > 0) {// no need to use such weights + if (weight > 0) {// no need to use such weights boneBuffersForVertex.put(weight, entry.getValue()); } } } - if(boneBuffersForVertex.size() == 0) {// attach the vertex to zero-indexed bone so that it does not collapse to (0, 0, 0) + if (boneBuffersForVertex.size() == 0) {// attach the vertex to zero-indexed bone so that it does not collapse to (0, 0, 0) boneBuffersForVertex.put(1.0f, 0); } boneBuffers.add(boneBuffersForVertex); @@ -591,7 +602,9 @@ public class TemporalMesh extends Geometry { if (materialIndex >= 0 && materials != null && materials.length > materialIndex && materials[materialIndex] != null) { materials[materialIndex].applyMaterial(geometry, meshStructure.getOldMemoryAddress(), entry.getValue().getUvCoords(), blenderContext); } else { - geometry.setMaterial(blenderContext.getDefaultMaterial()); + Material defaultMaterial = blenderContext.getDefaultMaterial().clone(); + defaultMaterial.getAdditionalRenderState().setFaceCullMode(FaceCullMode.Off); + geometry.setMaterial(defaultMaterial); } } } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/objects/ObjectHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/objects/ObjectHelper.java index 8c0acf9d4..8e2dc2652 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/objects/ObjectHelper.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/objects/ObjectHelper.java @@ -182,9 +182,9 @@ public class ObjectHelper extends AbstractBlenderHelper { if (pCurve.isNotNull()) { CurvesHelper curvesHelper = blenderContext.getHelper(CurvesHelper.class); Structure curveData = pCurve.fetchData().get(0); - List curves = curvesHelper.toCurve(curveData, blenderContext); - for (Geometry curve : curves) { - result.attachChild(curve); + TemporalMesh curvesTemporalMesh = curvesHelper.toCurve(curveData, blenderContext); + if(curvesTemporalMesh != null) { + result.attachChild(curvesTemporalMesh); } } break;