/*
* Copyright (c) 2009-2012 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
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.
*
* @author Marcin Roguski (Kaelthas)
*/
public class CurvesHelper extends AbstractBlenderHelper {
private static final Logger LOGGER = Logger.getLogger(CurvesHelper.class.getName());
/** Minimum basis U function degree for NURBS curves and surfaces. */
protected int minimumBasisUFunctionDegree = 4;
/** Minimum basis V function degree for NURBS curves and surfaces. */
protected int minimumBasisVFunctionDegree = 4;
/**
* This constructor parses the given blender version and stores the result. Some functionalities may differ in
* different blender versions.
* @param blenderVersion
* the version read from the blend file
* @param blenderContext
* the blender context
*/
public CurvesHelper(String blenderVersion, BlenderContext blenderContext) {
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));
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);
}
}
}
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, 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.
*
* @param bevel
* the bevel to be transformed
* @param prevPos
* previous curve point
* @param currPos
* current curve point (here the center of the new bevel will be
* set)
* @param nextPos
* next curve point
* @return points of transformed bevel
*/
private Vector3f[] transformBevel(Vector3f[] bevel, Vector3f prevPos, Vector3f currPos, Vector3f nextPos) {
bevel = bevel.clone();
// currPos and directionVector define the line in 3D space
Vector3f directionVector = prevPos != null ? currPos.subtract(prevPos) : nextPos.subtract(currPos);
directionVector.normalizeLocal();
// plane is described by equation: Ax + By + Cz + D = 0 where planeNormal = [A, B, C] and D = -(Ax + By + Cz)
Vector3f planeNormal = null;
if (prevPos != null) {
planeNormal = currPos.subtract(prevPos).normalizeLocal();
if (nextPos != null) {
planeNormal.addLocal(nextPos.subtract(currPos).normalizeLocal()).normalizeLocal();
}
} else {
planeNormal = nextPos.subtract(currPos).normalizeLocal();
}
float D = -planeNormal.dot(currPos);// D = -(Ax + By + Cz)
// now we need to compute paralell cast of each bevel point on the plane, the leading line is already known
// parametric equation of a line: x = px + vx * t; y = py + vy * t; z = pz + vz * t
// where p = currPos and v = directionVector
// using x, y and z in plane equation we get value of 't' that will allow us to compute the point where plane and line cross
float temp = planeNormal.dot(directionVector);
for (int i = 0; i < bevel.length; ++i) {
float t = -(planeNormal.dot(bevel[i]) + D) / temp;
if (fixUpAxis) {
bevel[i] = new Vector3f(bevel[i].x + directionVector.x * t, bevel[i].y + directionVector.y * t, bevel[i].z + directionVector.z * t);
} else {
bevel[i] = new Vector3f(bevel[i].x + directionVector.x * t, -bevel[i].z + directionVector.z * t, bevel[i].y + directionVector.y * t);
}
}
return bevel;
}
/**
* This method transforms the first line of the bevel points positioning it
* on the first point of the curve.
*
* @param startingLinePoints
* the vbevel shape points
* @param firstCurvePoint
* the first curve's point
* @param secondCurvePoint
* the second curve's point
* @return points of transformed bevel
*/
private 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);
Matrix4f m = new Matrix4f();
m.setRotationQuaternion(pointRotation);
m.setTranslation(firstCurvePoint);
float[] temp = new float[] { 0, 0, 0, 1 };
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]);
}
}
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());
}
}
}