浏览代码

Feature: curves and surfaces now have their own temporal mesh which
allows to apply modifiers on them.

jmekaelthas 10 年之前
父节点
当前提交
c90e50bb2e

+ 14 - 5
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<Structure> bezTriples, final int dimension) {
+        this(type, bezTriples, dimension, false);
+    }
+
+    @SuppressWarnings("unchecked")
+    public BezierCurve(final int type, final List<Structure> 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<Number> vec = (DynamicArray<Number>) 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<Vector3f> getControlPoints() {
         List<Vector3f> controlPoints = new ArrayList<Vector3f>(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;
     }

+ 14 - 693
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<Geometry> 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<Structure> nurbStructures = ((Structure) curveStructure.getFieldValue("nurb")).evaluateListBase();
-        Map<Number, List<Structure>> nurbs = new HashMap<Number, List<Structure>>();
-        for (Structure nurb : nurbStructures) {
-            Number matNumber = (Number) nurb.getFieldValue("mat_nr");
-            List<Structure> nurbList = nurbs.get(matNumber);
-            if (nurbList == null) {
-                nurbList = new ArrayList<Structure>();
-                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<Geometry> 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<Vector3f> conrtolPoints = new ArrayList<Vector3f>(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<Geometry>(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<Geometry>(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<Geometry> result = new ArrayList<Geometry>(nurbs.size());
-        for (Entry<Number, List<Structure>> nurbEntry : nurbs.entrySet()) {
-            for (Structure nurb : nurbEntry.getValue()) {
-                int type = ((Number) nurb.getFieldValue("type")).intValue();
-                List<Geometry> 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<Geometry> loadBezierCurve(Vector3f loc, Structure nurb, List<Geometry> bevelObject, Spline taperObject, BlenderContext blenderContext) throws BlenderFileException {
-        Pointer pBezierTriple = (Pointer) nurb.getFieldValue("bezt");
-        List<Geometry> result = new ArrayList<Geometry>();
-        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<Vector3f> 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<Vector3f> taperControlPoints = new ArrayList<Vector3f>(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<Geometry> loadNurb(Vector3f loc, Structure nurb, List<Geometry> bevelObject, Spline taperObject, BlenderContext blenderContext) throws BlenderFileException {
-        // loading the knots
-        List<Float>[] 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<Float>(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<Structure> bPoints = ((Pointer) nurb.getFieldValue("bp")).fetchData();
-        List<List<Vector4f>> controlPoints = new ArrayList<List<Vector4f>>(pntsV);
-        for (int i = 0; i < pntsV; ++i) {
-            List<Vector4f> uControlPoints = new ArrayList<Vector4f>(pntsU);
-            for (int j = 0; j < pntsU; ++j) {
-                DynamicArray<Float> vec = (DynamicArray<Float>) 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<Geometry> 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<Geometry>(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<Geometry>(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<Float> 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<Geometry> applyBevelAndTaper(Curve curve, List<Geometry> 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<Vector3f[]> bevels = new ArrayList<Vector3f[]>(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<Geometry> result = new ArrayList<Geometry>(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<Integer, Vector3f> normalMap = new TreeMap<Integer, Vector3f>();
-        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<Integer, Vector3f> 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<Integer, Vector3f> 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<Structure> 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<Vector3f> 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<Number> locArray = (DynamicArray<Number>) 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());
-        }
-    }
 }

+ 890 - 0
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<BezierLine>   beziers        = new ArrayList<BezierLine>();
+    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<Number, List<Structure>> nurbs = new HashMap<Number, List<Structure>>();
+        List<Structure> nurbStructures = ((Structure) curveStructure.getFieldValue("nurb")).evaluateListBase();
+        for (Structure nurb : nurbStructures) {
+            Number matNumber = (Number) nurb.getFieldValue("mat_nr");
+            List<Structure> nurbList = nurbs.get(matNumber);
+            if (nurbList == null) {
+                nurbList = new ArrayList<Structure>();
+                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<Number> scaleArray = (DynamicArray<Number>) 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<Number, List<Structure>> 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<Vector3f> 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<Float>[] 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<Float>(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<Structure> bPoints = ((Pointer) nurb.getFieldValue("bp")).fetchData();
+        List<List<Vector4f>> controlPoints = new ArrayList<List<Vector4f>>(pntsV);
+        for (int i = 0; i < pntsV; ++i) {
+            List<Vector4f> uControlPoints = new ArrayList<Vector4f>(pntsU);
+            for (int j = 0; j < pntsU; ++j) {
+                DynamicArray<Float> vec = (DynamicArray<Float>) 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<Number> scaleArray = (DynamicArray<Number>) 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<Vector3f> conrtolPoints = new ArrayList<Vector3f>();
+
+                // 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<BezierLine> getScaledBeziers() {
+        if (scale.equals(Vector3f.UNIT_XYZ)) {
+            return beziers;
+        }
+        List<BezierLine> result = new ArrayList<BezierLine>();
+        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<BezierLine> bevelBezierLines = bevelObject.getScaledBeziers();
+        List<BezierLine> 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<Vector3f[]> bevels = new ArrayList<Vector3f[]>();
+
+                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<Integer> capIndexes = new ArrayList<Integer>(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<Integer, Vector3f> normalMap = new TreeMap<Integer, Vector3f>();
+        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<Integer, Vector3f> 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<Vector3f> result = new ArrayList<Vector3f>();
+            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;
+        }
+    }
+}

+ 13 - 1
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;
     }
 
     /**

+ 34 - 7
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<Vector3f> 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 <b>true</b> 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]";
         }

+ 39 - 26
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<Integer> 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<String, Integer> 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);
             }
         }
     }

+ 3 - 3
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<Geometry> 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;