1/*
2 * To change this template, choose Tools | Templates
3 * and open the template in the editor.
4 */
5package com.jme3.bullet.collision.shapes;
6
7import com.jme3.export.InputCapsule;
8import com.jme3.export.JmeExporter;
9import com.jme3.export.JmeImporter;
10import com.jme3.export.OutputCapsule;
11import com.jme3.math.FastMath;
12import com.jme3.math.Vector3f;
13import com.jme3.scene.Mesh;
14import com.jme3.util.BufferUtils;
15import java.io.IOException;
16import java.nio.ByteBuffer;
17import java.util.logging.Level;
18import java.util.logging.Logger;
19
20/**
21 * Uses Bullet Physics Heightfield terrain collision system. This is MUCH faster
22 * than using a regular mesh.
23 * There are a couple tricks though:
24 *	-No rotation or translation is supported.
25 *	-The collision bbox must be centered around 0,0,0 with the height above and below the y-axis being
26 *	equal on either side. If not, the whole collision box is shifted vertically and things don't collide
27 *	as they should.
28 *
29 * @author Brent Owens
30 */
31public class HeightfieldCollisionShape extends CollisionShape {
32
33    protected int heightStickWidth;
34    protected int heightStickLength;
35    protected float[] heightfieldData;
36    protected float heightScale;
37    protected float minHeight;
38    protected float maxHeight;
39    protected int upAxis;
40    protected boolean flipQuadEdges;
41    protected ByteBuffer bbuf;
42//    protected FloatBuffer fbuf;
43
44    public HeightfieldCollisionShape() {
45    }
46
47    public HeightfieldCollisionShape(float[] heightmap) {
48        createCollisionHeightfield(heightmap, Vector3f.UNIT_XYZ);
49    }
50
51    public HeightfieldCollisionShape(float[] heightmap, Vector3f scale) {
52        createCollisionHeightfield(heightmap, scale);
53    }
54
55    protected void createCollisionHeightfield(float[] heightmap, Vector3f worldScale) {
56        this.scale = worldScale;
57        this.heightScale = 1;//don't change away from 1, we use worldScale instead to scale
58
59        this.heightfieldData = heightmap;
60
61        float min = heightfieldData[0];
62        float max = heightfieldData[0];
63        // calculate min and max height
64        for (int i = 0; i < heightfieldData.length; i++) {
65            if (heightfieldData[i] < min) {
66                min = heightfieldData[i];
67            }
68            if (heightfieldData[i] > max) {
69                max = heightfieldData[i];
70            }
71        }
72        // we need to center the terrain collision box at 0,0,0 for BulletPhysics. And to do that we need to set the
73        // min and max height to be equal on either side of the y axis, otherwise it gets shifted and collision is incorrect.
74        if (max < 0) {
75            max = -min;
76        } else {
77            if (Math.abs(max) > Math.abs(min)) {
78                min = -max;
79            } else {
80                max = -min;
81            }
82        }
83        this.minHeight = min;
84        this.maxHeight = max;
85
86        this.upAxis = 1;
87        this.flipQuadEdges = false;
88
89        heightStickWidth = (int) FastMath.sqrt(heightfieldData.length);
90        heightStickLength = heightStickWidth;
91
92
93        createShape();
94    }
95
96    protected void createShape() {
97        bbuf = BufferUtils.createByteBuffer(heightfieldData.length * 4);
98//        fbuf = bbuf.asFloatBuffer();//FloatBuffer.wrap(heightfieldData);
99//        fbuf.rewind();
100//        fbuf.put(heightfieldData);
101        for (int i = 0; i < heightfieldData.length; i++) {
102            float f = heightfieldData[i];
103            bbuf.putFloat(f);
104        }
105//        fbuf.rewind();
106        objectId = createShape(heightStickWidth, heightStickLength, bbuf, heightScale, minHeight, maxHeight, upAxis, flipQuadEdges);
107        Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Created Shape {0}", Long.toHexString(objectId));
108        setScale(scale);
109        setMargin(margin);
110    }
111
112    private native long createShape(int heightStickWidth, int heightStickLength, ByteBuffer heightfieldData, float heightScale, float minHeight, float maxHeight, int upAxis, boolean flipQuadEdges);
113
114    public Mesh createJmeMesh() {
115        //TODO return Converter.convert(bulletMesh);
116        return null;
117    }
118
119    public void write(JmeExporter ex) throws IOException {
120        super.write(ex);
121        OutputCapsule capsule = ex.getCapsule(this);
122        capsule.write(heightStickWidth, "heightStickWidth", 0);
123        capsule.write(heightStickLength, "heightStickLength", 0);
124        capsule.write(heightScale, "heightScale", 0);
125        capsule.write(minHeight, "minHeight", 0);
126        capsule.write(maxHeight, "maxHeight", 0);
127        capsule.write(upAxis, "upAxis", 1);
128        capsule.write(heightfieldData, "heightfieldData", new float[0]);
129        capsule.write(flipQuadEdges, "flipQuadEdges", false);
130    }
131
132    public void read(JmeImporter im) throws IOException {
133        super.read(im);
134        InputCapsule capsule = im.getCapsule(this);
135        heightStickWidth = capsule.readInt("heightStickWidth", 0);
136        heightStickLength = capsule.readInt("heightStickLength", 0);
137        heightScale = capsule.readFloat("heightScale", 0);
138        minHeight = capsule.readFloat("minHeight", 0);
139        maxHeight = capsule.readFloat("maxHeight", 0);
140        upAxis = capsule.readInt("upAxis", 1);
141        heightfieldData = capsule.readFloatArray("heightfieldData", new float[0]);
142        flipQuadEdges = capsule.readBoolean("flipQuadEdges", false);
143        createShape();
144    }
145}
146