# Realistic waves for ship simulation? [FIXED]

Hi there,

I have been looking around for a while but don’t seem to find a solution (or at least, I fail to combine the findings in a solution for me). The topic is clear I suppose…

Anyway, for a ship simulation I want the ocean to have waves and of course,the waves have to apply forces on the ship, so it will roll on the waves. My current idea is to have several control-positions on the ship which will detect whether they are above or below the water surface. When below, they will apply a force upwards, when above, there will be no force and the gravity will pull the ship down to the water again.

Creating a real 3D-ocean seems over-kill to me and I think it should be somehow possible to get the height of the waves generated by the waterfilter for every control-position (or one of the other wave-generators around here). Do you think this is a way forward (and if so, where to start), or would there be other (read: better) solutions?

Regards,

Husky

Well, I managed to get a prototype working! I ended up with 6 control points (two at the bow, two mid-ships and two at the stern of the ship. Only four would make the the ships mid-ship being flushed or lifted out of the water. So I added 2 more mid-ships. The ones at the bow generate the smallest up-force, mid-ship the biggest and the stern somewhere in between.

I now see that when frame rate drops below 30, the ships starts to jump and finaly keeps flipping over in teh most strangest ways. Anyone any idea why this happends?

Here is what I have in the prePhysicsTick (grid is the water, the waveXXX and shipXXX are referring to the control points):

[java]public void prePhysicsTick(PhysicsSpace space, float f)

{

//ship_phy.applyForce(new Vector3f(1f, 0, 0), new Vector3f(ship_phy.getPhysicsLocation()));

//ship_phy.applyTorque(new Vector3f(0f, 0.050f, 0f));

float[] angles = new float[3];

cam.getRotation().toAngles(angles);

grid.update(cam.getViewMatrix().clone());

float waveBSB = whGEN.getHeight(ship.getChild("buoyBSB").getWorldTranslation().x, ship.getChild("buoyBSB").getWorldTranslation().z, grid.myTime);

float shipBSB = ship.getChild("buoyBSB").getWorldTranslation().y;

float waveBP = whGEN.getHeight(ship.getChild("buoyBP").getWorldTranslation().x, ship.getChild("buoyBP").getWorldTranslation().z, grid.myTime);

float shipBP = ship.getChild("buoyBP").getWorldTranslation().y;

float waveSSB = whGEN.getHeight(ship.getChild("buoySSB").getWorldTranslation().x, ship.getChild("buoySSB").getWorldTranslation().z, grid.myTime);

float shipSSB = ship.getChild("buoySSB").getWorldTranslation().y;

float waveSP = whGEN.getHeight(ship.getChild("buoySP").getWorldTranslation().x, ship.getChild("buoySP").getWorldTranslation().z, grid.myTime);

float shipSP = ship.getChild("buoySP").getWorldTranslation().y;

float waveMSB = whGEN.getHeight(ship.getChild("buoyMSB").getWorldTranslation().x, ship.getChild("buoyMSB").getWorldTranslation().z, grid.myTime);

float shipMSB = ship.getChild("buoyMSB").getWorldTranslation().y;

float waveMP = whGEN.getHeight(ship.getChild("buoyMP").getWorldTranslation().x, ship.getChild("buoyMP").getWorldTranslation().z, grid.myTime);

float shipMP = ship.getChild("buoyMP").getWorldTranslation().y;

if (shipBSB < waveBSB)

{

ship_phy.applyForce(new Vector3f(0, (waveBSB-shipBSB)*waveForceBow, 0), new Vector3f(ship.getChild("buoyBSB").getWorldTranslation().x, ship.getChild("buoyBSB").getWorldTranslation().y, ship.getChild("buoyBSB").getWorldTranslation().z));

}

if (shipBP < waveBP)

{

ship_phy.applyForce(new Vector3f(0, (waveBP-shipBP)*waveForceBow, 0), new Vector3f(ship.getChild("buoyBP").getWorldTranslation().x, ship.getChild("buoyBP").getWorldTranslation().y, ship.getChild("buoyBP").getWorldTranslation().z));

}

if (shipSSB < waveSSB)

{

ship_phy.applyForce(new Vector3f(0, (waveSSB-shipSSB)*waveForceStern, 0), new Vector3f(ship.getChild("buoySSB").getWorldTranslation().x, ship.getChild("buoySSB").getWorldTranslation().y, ship.getChild("buoySSB").getWorldTranslation().z));

}

if (shipSP < waveSP)

{

ship_phy.applyForce(new Vector3f(0, (waveSP-shipSP)*waveForceStern, 0), new Vector3f(ship.getChild("buoySP").getWorldTranslation().x, ship.getChild("buoySP").getWorldTranslation().y, ship.getChild("buoySP").getWorldTranslation().z));

}

if (shipMP < waveMP)

{

ship_phy.applyForce(new Vector3f(0, (waveMP-shipMP)*waveForceMid, 0), new Vector3f(ship.getChild("buoyMP").getWorldTranslation().x, ship.getChild("buoyMP").getWorldTranslation().y, ship.getChild("buoyMP").getWorldTranslation().z));

}

if (shipMSB < waveMSB)

{

ship_phy.applyForce(new Vector3f(0, (waveMSB-shipMSB)*waveForceMid, 0), new Vector3f(ship.getChild("buoyMSB").getWorldTranslation().x, ship.getChild("buoyMSB").getWorldTranslation().y, ship.getChild("buoyMSB").getWorldTranslation().z));

}

}[/java]

1 Like

Since you have an equation that describes your waves (and I think that the shader works like this aswell) you can determine the height of the wave at every position by evaluating the wave equation there…

I saw this one, dunno if the author have shared it.

EDIT: Ahh, he did... Here it is: http://hub.jmonkeyengine.org/groups/user-code-projects/forum/topic/projectedgrid-and-legacy-water-effects-from-jme-2/

you can check this out maybe that can help

http://hub.jmonkeyengine.org/groups/user-code-projects/forum/topic/projectedgrid-and-legacy-water-effects-from-jme-2/#post-169290

The idea of having 4 (that would probably enough) points where you sample the wave equation and moving the ship accordingly sounds like it should get things moving around nicely with a bit of tweaking.

Ah, thanks! I took a look at it and think I know how to use it. I will give it a shot. Sounds like some sleepless nights again

I am not sure about the number of control points yet, but trial-and-error should answer this. I will try to make the upward-force depending on the distance between the control point and the water surface (the deeper the hull is under water, the higher the upward-force has to be). Some damping has to be implemented to make sure the ship is not tipping over too soon.

In rest, the force on each control point will be the equal of the ships mass divided by the number of control points. This should keep the ship floating. I only hope the ship will not start to spin uncontrollable…

Well, time will tell!!!

Frame rates are desperately low, but that could be my PC just as well.
2 Likes

That motion is looking very realistic, nice work. The ship is riding rather low in the water but all you need to do is fix that and add some wake/foam/splash effects and it will be rocking (no puns intended).

@husky said:
Thanks Normen, that worked.

http://youtu.be/IHgq8BSSZmc

Frame rates are desperately low, but that could be my PC just as well.

Looks already very nice. Only a little bit tweaking ;).

I'm using like 3600 ContactPoints in my solution (convex/concave objects) but im in the simulation business so i always want a little bit more :P.

Those were awful big waves. Maybe the captain should’ve stayed at port while this storm was hitting. Oh and you might want to inform him that he should take those waves head on. Unless he’s suicidal.

Looks really good though.

Looks cool yeah

Thanks

If only I had a bigger PC… As soon as I start recording, frame rates drop drastically and the ship turns into a submarine-wannabe! I don’t know what to expect when I start with particle systems

Anyway, lot’s of work still to be done. It started with modeling the destroyer and halfway I took the challenge to start creating a game. Most likely way over my head, but hey, it is fun! We’ll see what comes out.

@husky would you please post the code for the waves simulation? I’d love to use it.

@Pixelapp

Well, pasting the code is quite a hassle, so I have something better (at least, I THINK it is better ):

OceanBuoyancy Project

Here you can download a project which you can open in the JME SDK. Just unzip it and add it as a new poject. It should give you a working version of an ocean with a ships hull floating on it. When you launch it, you will see an ocean with a huge red box (just for testing) and the hull of a ship. With ‘Page Up’ and ‘Page Down’ you can increase and decrease the sea state. There are 8 different states.

Still a lot of optimalizations to do, but I do not have much time lately and thus, have not worked on it any further. But the buoyancy as it is now is in there! Feel free to fiddle around with the values (also for the sea states to get a good result.

Hopefully this will get you (and maybe others) started!

1 Like

Here’s the code for the Jmonkey project:

[java]package formulas;

import com.jme3.math.FastMath;

/**
*

• @author Arjen
/
public class Formulas
{
public float truncate(float x, int dec)
{
long y =(long)(x
(dec10));
return (float)y/(dec
10);
}

/**

• @return compass heading in degrees (0° - 360°)
/
{
-1;
{
}
}

/**

• @param hdng the compass heading in degrees (0° - 360°)

*/
{

{
}
{
}
}

/**

• Get the smallest angle between two headings
• @return smallAngle
/
public float getSmallestAngle(float currot, float setrot) {
float curRot = currot;
float setRot = setrot;
float smallAngle = setRot - curRot;
if (smallAngle > FastMath.PI) {
smallAngle -= 2f
FastMath.PI;
}
if (smallAngle < -FastMath.PI) {
smallAngle += 2f*FastMath.PI;
}
return smallAngle;
}

/**

• Convert integer to string which can play the sound

• @param number the number to be converted

• @return soundString
*/
public String[] numberToSoundString(int number) {
String myNumber = Integer.toString(number);

String[] soundString = new String[myNumber.length()];
for (int i=0; i<myNumber.length(); i++) {
if (i<myNumber.length()) {
soundString[i] = myNumber.substring(0+i, 1+i);
}
}
return soundString;
}

/**

• Check if string is a valid integer
• @param input the string to be checked
• @return boolean
*/
public boolean isInteger(String input) {
try {
Integer.parseInt(input);
return true;
} catch (NumberFormatException nfe) {
return false;
}
}

}[/java]

[java]
package ocean;

interface HeightGenerator {
public float getHeight( float x, float z, float time );
}
[/java]

[java]
/*

• 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 ocean;

import java.nio.FloatBuffer;
import java.nio.IntBuffer;

import com.jme3.math.FastMath;
import com.jme3.math.Matrix4f;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.math.Vector4f;
import com.jme3.renderer.Camera;
import com.jme3.scene.Mesh;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.system.Timer;
import com.jme3.util.BufferUtils;
import javax.vecmath.TexCoord2f;

/**

• `ProjectedGrid`

• Projected grid mesh

• @author Rikard Herlitz (MrCoder)

• @author Matthias Schellhase portage to jme3
*/
public class MyProjectedGrid extends Mesh {

private static final long serialVersionUID = 1L;
private int sizeX;
private int sizeY;

private static Vector3f calcVec1 = new Vector3f();
private static Vector3f calcVec2 = new Vector3f();
private static Vector3f calcVec3 = new Vector3f();
private FloatBuffer vertBuf;
private FloatBuffer normBuf;
private FloatBuffer texs;
private IntBuffer indexBuffer;
private float viewPortWidth = 0;
private float viewPortHeight = 0;
private Vector2f source = new Vector2f();
private Matrix4f projectionMatrix = new Matrix4f();
private Matrix4f modelViewProjectionInverse = new Matrix4f();
private Vector4f intersectBottomLeft = new Vector4f();
private Vector4f intersectTopLeft = new Vector4f();
private Vector4f intersectTopRight = new Vector4f();
private Vector4f intersectBottomRight = new Vector4f();
private Matrix4f modelViewMatrix1 = new Matrix4f();
private Matrix4f projectionMatrix1 = new Matrix4f();
private Matrix4f modelViewProjection1 = new Matrix4f();
private Matrix4f modelViewProjectionInverse1 = new Matrix4f();
private Vector4f intersectBottomLeft1 = new Vector4f();
private Vector4f intersectTopLeft1 = new Vector4f();
private Vector4f intersectTopRight1 = new Vector4f();
private Vector4f intersectBottomRight1 = new Vector4f();
private Vector3f camloc = new Vector3f();
private Vector3f camdir = new Vector3f();
private Vector4f pointFinal = new Vector4f();
private Vector4f pointTop = new Vector4f();
private Vector4f pointBottom = new Vector4f();
private Vector3f realPoint = new Vector3f();
public boolean freezeProjector = false;
public boolean useReal = false;
private Vector3f projectorLoc = new Vector3f();
private Timer timer;
private Camera cam;
private float height;
private float fovY = 45.0f;
private WaterHeightGenerator heightGenerator;
private float textureScale;
private float[] vertBufArray;
private float[] normBufArray;
private float[] texBufArray;

public float myTime;
public WaterHeightGenerator myHeightGenerator;

public MyProjectedGrid(Timer timer, Camera cam, int sizeX, int sizeY, float texureScale, WaterHeightGenerator heightGenerator) {
this.sizeX = sizeX;
this.sizeY = sizeY;
this.textureScale = texureScale;
this.heightGenerator = heightGenerator;
myHeightGenerator = this.heightGenerator;
this.cam = cam;

`````` if (cam.getFrustumNear() &gt; 0.0f) {
fovY = FastMath.atan(cam.getFrustumTop() / cam.getFrustumNear())
}

this.timer = timer;

vertBufArray = new float[sizeX * sizeY * 3];
normBufArray = new float[sizeX * sizeY * 3];
texBufArray = new float[sizeX * sizeY * 2];

buildVertices();
buildTextureCoordinates();
buildNormals();
``````

}

public void switchFreeze() {
freezeProjector = !freezeProjector;
}

public float getTextureScale() {
return textureScale;
}

public void setTextureScale(float textureScale) {
this.textureScale = textureScale;
}

public void update(Matrix4f modelViewMatrix) {
if (freezeProjector) {
return;
}

`````` float time = timer.getTimeInSeconds();
myTime = time;

camloc.set(cam.getLocation());
camdir.set(cam.getDirection());

height = cam.getLocation().getY();

Camera camera = cam;
ProjectedTextureUtil.camera = camera.clone();
viewPortWidth = camera.getWidth();
viewPortHeight = camera.getHeight();

modelViewMatrix.set(camera.getViewMatrix().clone());
modelViewMatrix.transposeLocal();

projectionMatrix.set(camera.getProjectionMatrix().clone());
projectionMatrix.transposeLocal();

modelViewProjectionInverse.set(modelViewMatrix).multLocal(projectionMatrix);
modelViewProjectionInverse.invertLocal();

source.set(0.5f, 0.5f);
getWorldIntersection(height, source, modelViewProjectionInverse, pointFinal);

pointFinal.multLocal(1.0f / pointFinal.getW());
realPoint.set(pointFinal.getX(), pointFinal.getY(), pointFinal.getZ());
projectorLoc.set(cam.getLocation());

Matrix4f rangeMatrix = null;
if (useReal) {
Vector3f fakeLoc = new Vector3f(projectorLoc);
Vector3f fakePoint = new Vector3f(realPoint);

rangeMatrix = getMinMax(fakeLoc, fakePoint, cam);
}

ProjectedTextureUtil.matrixLookAt(projectorLoc, realPoint, Vector3f.UNIT_Y, modelViewMatrix);
modelViewMatrix.transposeLocal();

ProjectedTextureUtil.matrixProjection(fovY + 30.0f, viewPortWidth / viewPortHeight, cam.getFrustumNear(), cam.getFrustumFar(), projectionMatrix);
projectionMatrix.transposeLocal();

modelViewProjectionInverse.set(modelViewMatrix).multLocal(projectionMatrix);
modelViewProjectionInverse.invertLocal();

if (useReal &amp;&amp; rangeMatrix != null) {
rangeMatrix.multLocal(modelViewProjectionInverse);
modelViewProjectionInverse.set(rangeMatrix);
}

source.set(0, 0);
getWorldIntersection(height, source, modelViewProjectionInverse, intersectBottomLeft);

source.set(0, 1);
getWorldIntersection(height, source, modelViewProjectionInverse, intersectTopLeft);
source.set(1, 1);
getWorldIntersection(height, source, modelViewProjectionInverse, intersectTopRight);
source.set(1, 0);
getWorldIntersection(height, source, modelViewProjectionInverse, intersectBottomRight);

vertBuf.rewind();
float du = 1.0f / (float) (sizeX - 1);
float dv = 1.0f / (float) (sizeY - 1);
float u = 0, v = 0;
int index = 0;
for (int y = 0; y &lt; sizeY; y++) {
for (int x = 0; x &lt; sizeX; x++) {
interpolate(intersectTopLeft, intersectTopRight, u, pointTop);
interpolate(intersectBottomLeft, intersectBottomRight, u, pointBottom);
interpolate(pointTop, pointBottom, v, pointFinal);
pointFinal.x /= pointFinal.w;
pointFinal.z /= pointFinal.w;
realPoint.set(pointFinal.getX(),
myHeightGenerator.getHeight(pointFinal.getX(), pointFinal.getZ(), time),
pointFinal.getZ());

vertBufArray[index++] = realPoint.getX();
vertBufArray[index++] = realPoint.getY();
vertBufArray[index++] = realPoint.getZ();

u += du;
}
v += dv;
u = 0;
}
vertBuf.put(vertBufArray);
getBuffer(Type.Position).updateData(vertBuf);
updateBound();

// Texture stuff
texs.rewind();
for (int i = 0; i &lt; getVertexCount(); i++) {
texBufArray[i * 2] = vertBufArray[i * 3] * textureScale;
texBufArray[i * 2 + 1] = vertBufArray[i * 3 + 2] * textureScale;
}
texs.put(texBufArray);
getBuffer(Type.TexCoord).updateData(texs);

normBuf.rewind();
oppositePoint.set(0, 0, 0);
rootPoint.set(0, 0, 0);
tempNorm.set(0, 0, 0);
int adj = 0, opp = 0, normalIndex = 0;
for (int row = 0; row &lt; sizeY; row++) {
for (int col = 0; col &lt; sizeX; col++) {
if (row == sizeY - 1) {
if (col == sizeX - 1) { // last row, last col
// up cross left
opp = normalIndex - 1;
} else { // last row, except for last col
// right cross up
opp = normalIndex - sizeX;
}
} else {
if (col == sizeX - 1) { // last column except for last row
// left cross down
opp = normalIndex + sizeX;
} else { // most cases
// down cross right
opp = normalIndex + 1;
}
}
rootPoint.set(vertBufArray[normalIndex * 3], vertBufArray[normalIndex * 3 + 1], vertBufArray[normalIndex * 3 + 2]);
oppositePoint.set(vertBufArray[opp * 3], vertBufArray[opp * 3 + 1], vertBufArray[opp * 3 + 2]);

normBufArray[normalIndex * 3] = tempNorm.x;
normBufArray[normalIndex * 3 + 1] = tempNorm.y;
normBufArray[normalIndex * 3 + 2] = tempNorm.z;

normalIndex++;
}
}
normBuf.put(normBufArray);
getBuffer(Type.Normal).updateData(normBuf);
``````

}

private Matrix4f getMinMax(Vector3f fakeLoc, Vector3f fakePoint, Camera cam) {
Matrix4f rangeMatrix;
ProjectedTextureUtil.matrixLookAt(fakeLoc, fakePoint, Vector3f.UNIT_Y, modelViewMatrix1);
ProjectedTextureUtil.matrixProjection(fovY, viewPortWidth / viewPortHeight, cam.getFrustumNear(), cam.getFrustumFar(), projectionMatrix1);
modelViewProjection1.set(modelViewMatrix1).multLocal(projectionMatrix1);
modelViewProjectionInverse1.set(modelViewProjection1).invertLocal();

`````` source.set(0, 0);
getWorldIntersection(height, source, modelViewProjectionInverse, intersectBottomLeft1);
source.set(0, 1);
getWorldIntersection(height, source, modelViewProjectionInverse, intersectTopLeft1);
source.set(1, 1);
getWorldIntersection(height, source, modelViewProjectionInverse, intersectTopRight1);
source.set(1, 0);
getWorldIntersection(height, source, modelViewProjectionInverse, intersectBottomRight1);

Vector3f tmp = new Vector3f();
tmp.set(intersectBottomLeft.getX(), intersectBottomLeft.getY(), intersectBottomLeft.getZ());
modelViewProjection1.mult(tmp, tmp);
intersectBottomLeft.set(tmp.x, tmp.y, tmp.z, intersectBottomLeft.getW());

tmp.set(intersectTopLeft1.getX(), intersectTopLeft1.getY(), intersectTopLeft1.getZ());
modelViewProjection1.mult(tmp, tmp);
intersectTopLeft1.set(tmp.x, tmp.y, tmp.z, intersectTopLeft1.getW());

tmp.set(intersectTopRight1.getX(), intersectTopRight1.getY(), intersectTopRight1.getZ());
modelViewProjection1.mult(tmp, tmp);
intersectTopRight1.set(tmp.x, tmp.y, tmp.z, intersectTopRight1.getW());

tmp.set(intersectBottomRight1.getX(), intersectBottomRight1.getY(), intersectBottomRight1.getZ());
modelViewProjection1.mult(tmp, tmp);
intersectBottomRight1.set(tmp.x, tmp.y, tmp.z, intersectBottomRight1.getW());

float minX = Float.MAX_VALUE, minY = Float.MAX_VALUE, maxX = Float.MIN_VALUE, maxY = Float.MIN_VALUE;
if (intersectBottomLeft1.getX() &lt; minX) {
minX = intersectBottomLeft1.getX();
}
if (intersectTopLeft1.getX() &lt; minX) {
minX = intersectTopLeft1.getX();
}
if (intersectTopRight1.getX() &lt; minX) {
minX = intersectTopRight1.getX();
}
if (intersectBottomRight1.getX() &lt; minX) {
minX = intersectBottomRight1.getX();
}
if (intersectBottomLeft1.getX() &gt; maxX) {
maxX = intersectBottomLeft1.getX();
}
if (intersectTopLeft1.getX() &gt; maxX) {
maxX = intersectTopLeft1.getX();
}
if (intersectTopRight1.getX() &gt; maxX) {
maxX = intersectTopRight1.getX();
}
if (intersectBottomRight1.getX() &gt; maxX) {
maxX = intersectBottomRight1.getX();
}

if (intersectBottomLeft1.getY() &lt; minY) {
minY = intersectBottomLeft1.getY();
}
if (intersectTopLeft1.getY() &lt; minY) {
minY = intersectTopLeft1.getY();
}
if (intersectTopRight1.getY() &lt; minY) {
minY = intersectTopRight1.getY();
}
if (intersectBottomRight1.getY() &lt; minY) {
minY = intersectBottomRight1.getY();
}
if (intersectBottomLeft1.getY() &gt; maxY) {
maxY = intersectBottomLeft1.getY();
}
if (intersectTopLeft1.getY() &gt; maxY) {
maxY = intersectTopLeft1.getY();
}
if (intersectTopRight1.getY() &gt; maxY) {
maxY = intersectTopRight1.getY();
}
if (intersectBottomRight1.getY() &gt; maxY) {
maxY = intersectBottomRight1.getY();
}
rangeMatrix = new Matrix4f(
maxX - minX, 0, 0, minX,
0, maxY - minY, 0, minY,
0, 0, 1, 0,
0, 0, 0, 1);
rangeMatrix.transpose();
return rangeMatrix;
``````

}

private void interpolate(Vector4f beginVec, Vector4f finalVec, float changeAmnt, Vector4f resultVec) {
resultVec.x = (1 - changeAmnt) * beginVec.x + changeAmnt * finalVec.x;
resultVec.y = (1 - changeAmnt) * beginVec.y + changeAmnt * finalVec.y;
resultVec.z = (1 - changeAmnt) * beginVec.z + changeAmnt * finalVec.z;
resultVec.w = (1 - changeAmnt) * beginVec.w + changeAmnt * finalVec.w;
}

public static Vector4f getWorldIntersection(float camheight, Vector2f screenPosition, Matrix4f viewProjectionMatrix, Vector4f store) {
Vector4f origin = new Vector4f();
Vector4f direction = new Vector4f();

`````` origin.set(screenPosition.getX() * 2 - 1, screenPosition.getY() * 2 - 1, -1, 1);
direction.set(screenPosition.getX() * 2 - 1, screenPosition.getY() * 2 - 1, 1, 1);

origin = viewProjectionMatrix.transpose().mult(origin);
direction = viewProjectionMatrix.transpose().mult(direction);

if (camheight &gt; 0) {
if (direction.getY() &gt; 0) {
direction.y = 0;
}
} else {
if (direction.getY() &lt; 0) {
direction.y = 0;
}
}

direction.subtractLocal(origin);

float t = -origin.getY() / direction.getY();

direction.multLocal(t);
store.set(origin);
return store;
``````

}

@Override
public int getVertexCount() {
return sizeX * sizeY;
}

/**

• `getSurfaceNormal` returns the normal of an arbitrary point
• on the terrain. The normal is linearly interpreted from the normals of
• the 4 nearest defined points. If the point provided is not within the
• bounds of the height map, null is returned.
• @param position the vector representing the location to find a normal at.
• @param store the Vector3f object to store the result in. If null, a new one
• ``````            is created.
``````
• @return the normal vector at the provided location.
*/
public Vector3f getSurfaceNormal(Vector2f position, Vector3f store) {
return getSurfaceNormal(position.getX(), position.getY(), store);
}

/**

• `getSurfaceNormal` returns the normal of an arbitrary point
• on the terrain. The normal is linearly interpreted from the normals of
• the 4 nearest defined points. If the point provided is not within the
• bounds of the height map, null is returned.
• @param position the vector representing the location to find a normal at. Only
• ``````            the x and z values are used.
``````
• @param store the Vector3f object to store the result in. If null, a new one
• ``````            is created.
``````
• @return the normal vector at the provided location.
*/
public Vector3f getSurfaceNormal(Vector3f position, Vector3f store) {
return getSurfaceNormal(position.getX(), position.getZ(), store);
}

/**

• `getSurfaceNormal` returns the normal of an arbitrary point

• on the terrain. The normal is linearly interpreted from the normals of

• the 4 nearest defined points. If the point provided is not within the

• bounds of the height map, null is returned.

• @param x the x coordinate to check.

• @param z the z coordinate to check.

• @param store the Vector3f object to store the result in. If null, a new one

• ``````         is created.
``````
• @return the normal unit vector at the provided location.
*/
public Vector3f getSurfaceNormal(float x, float z, Vector3f store) {
float col = FastMath.floor(x);
float row = FastMath.floor(z);

if (col < 0 || row < 0 || col >= sizeX - 1 || row >= sizeY - 1) {
return null;
}
float intOnX = x - col, intOnZ = z - row;

if (store == null) {
store = new Vector3f();
}

Vector3f topLeft = store, topRight = calcVec1, bottomLeft = calcVec2, bottomRight = calcVec3;

int focalSpot = (int) (col + row * sizeX);

// find the heightmap point closest to this position (but will always
// be to the left ( < x) and above (< z) of the spot.
BufferUtils.populateFromBuffer(topLeft, normBuf, focalSpot);

// now find the next point to the right of topLeft’s position…
BufferUtils.populateFromBuffer(topRight, normBuf, focalSpot + 1);

// now find the next point below topLeft’s position…
BufferUtils.populateFromBuffer(bottomLeft, normBuf, focalSpot + sizeX);

// now find the next point below and to the right of topLeft’s
// position…
BufferUtils.populateFromBuffer(bottomRight, normBuf, focalSpot + sizeX + 1);

// Use linear interpolation to find the height.
topLeft.interpolate(topRight, intOnX);
bottomLeft.interpolate(bottomRight, intOnX);
topLeft.interpolate(bottomLeft, intOnZ);
}

/**

• `buildVertices` sets up the vertex and index arrays of the

• TriMesh.
*/
private void buildVertices() {
vertBuf = BufferUtils.createVector3Buffer(vertBuf, getVertexCount());

setBuffer(Type.Position, 3, vertBuf);

Vector3f point = new Vector3f();
for (int x = 0; x < sizeX; x++) {
for (int y = 0; y < sizeY; y++) {
point.set(x, 0, y);
BufferUtils.setInBuffer(point, vertBuf, (x + (y * sizeX)));
}
}

//set up the indices
int triangleQuantity = ((sizeX - 1) * (sizeY - 1)) * 2;
//setQuantity( triangleQuantity );
indexBuffer = BufferUtils.createIntBuffer(triangleQuantity * 3);

setBuffer(Type.Index, 3, indexBuffer);

//go through entire array up to the second to last column.
for (int i = 0; i < (sizeX * (sizeY - 1)); i++) {
//we want to skip the top row.
if (i % ((sizeX * (i / sizeX + 1)) - 1) == 0 && i != 0) {
// logger.info("skip row: “+i+” cause: "+((sizeY * (i / sizeX + 1)) - 1));
continue;
} else {
// logger.info("i: "+i);
}
//set the top left corner.
indexBuffer.put(i);
//set the bottom right corner.
indexBuffer.put((1 + sizeX) + i);
//set the top right corner.
indexBuffer.put(1 + i);
//set the top left corner
indexBuffer.put(i);
//set the bottom left corner
indexBuffer.put(sizeX + i);
//set the bottom right corner
indexBuffer.put((1 + sizeX) + i);
}
}

/**

• `buildTextureCoordinates` calculates the texture coordinates

• of the terrain.
*/
private void buildTextureCoordinates() {
texs = BufferUtils.createVector2Buffer(getVertexCount());

texs.clear();

vertBuf.rewind();
for (int i = 0; i < getVertexCount(); i++) {
texs.put(vertBuf.get() * textureScale);
vertBuf.get(); // ignore vert y coord.
texs.put(vertBuf.get() * textureScale);
}
setBuffer(Type.TexCoord, 2, texs);
}
/**

• `buildNormals` calculates the normals of each vertex that

• makes up the block of terrain.
*/
Vector3f oppositePoint = new Vector3f();
Vector3f rootPoint = new Vector3f();
Vector3f tempNorm = new Vector3f();

private void buildNormals() {
normBuf = BufferUtils.createVector3Buffer(normBuf, getVertexCount());
setBuffer(Type.Normal, 3, normBuf);

`````` oppositePoint.set(0, 0, 0);
rootPoint.set(0, 0, 0);
tempNorm.set(0, 0, 0);
int adj = 0, opp = 0, normalIndex = 0;
for (int row = 0; row &lt; sizeY; row++) {
for (int col = 0; col &lt; sizeX; col++) {
BufferUtils.populateFromBuffer(rootPoint, vertBuf, normalIndex);
if (row == sizeY - 1) {
if (col == sizeX - 1) { // last row, last col
// up cross left
opp = normalIndex - 1;
} else { // last row, except for last col
// right cross up
opp = normalIndex - sizeX;
}
} else {
if (col == sizeY - 1) { // last column except for last row
// left cross down
opp = normalIndex + sizeX;
} else { // most cases
// down cross right
opp = normalIndex + 1;
}
}
BufferUtils.populateFromBuffer(oppositePoint, vertBuf, opp);
BufferUtils.setInBuffer(tempNorm, normBuf, normalIndex);
normalIndex++;
}
}
``````

}
}

[/java]

[java]
/*

• 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 ocean;

import com.jme3.math.Matrix4f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;
import com.jme3.renderer.Renderer;
import com.jme3.texture.Texture;

/**

• `ProjectedTextureUtil`

• @author Rikard Herlitz (MrCoder)

• @author Joshua Ellen (basixs)

• [1-16-2009] - Abstracted, removed direct calls to openGL

• @author Matthias Schellhase portage to jme3
*/
public class ProjectedTextureUtil {

static Camera camera = null;
private static Matrix4f lightProjectionMatrix = new Matrix4f();
private static Matrix4f lightViewMatrix = new Matrix4f();
private static Matrix4f biasMatrix = new Matrix4f( 0.5f, 0.0f, 0.0f, 0.0f,
0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 0.5f, 0.0f, 0.5f, 0.5f, 0.5f,
1.0f ); // bias from [-1, 1] to [0, 1]

/**

• Updated texture matrix on the provided texture
• @param texture
• ``````       Texture to update texturematrix on
``````
• @param fov
• ``````       Projector field of view, in angles
``````
• @param aspect
• ``````       Projector frustum aspect ratio
``````
• @param near
• ``````       Projector frustum near plane
``````
• @param far
• ``````       Projector frustum far plane
``````
• @param pos
• ``````       Projector position
``````
• @param aim
• ``````       Projector look at position
``````

*/

/**

• Populates a `Matrix4f` with the proper look at transformations

• from the ModelView matrix.

• @param location the ‘Where’ in result matrix

• @param at the ‘At’ in the result matrix

• @param up the world up

• @param result the altered `Matrix4f`
*/
public static void matrixLookAt( Vector3f location, Vector3f at,
Vector3f up, Matrix4f result ) {

camera.setLocation( location );
camera.lookAt( at, up );

result.set( camera.getViewMatrix() );
}

/**

• Populates a `Matrix4f` with the proper frustum transformations

• from the ModelView matrix.

• @param fovY the Field of View

• @param aspect the aspect ratio

• @param near the near plane of the frustum

• @param far the far frame of the frustum

• @param result the altered `Matrix4f`
*/
public static void matrixPerspective( float fovY, float aspect,
float near, float far, Matrix4f result ) {

camera.setFrustumPerspective( fovY, aspect, near, far );

result.set( camera.getViewMatrix() );
}

/**

• Populates a `Matrix4f` with the proper frustum transformations

• from the Projection matrix.

• @param fovY the Field of View

• @param aspect the aspect ratio

• @param near the near plane of the frustum

• @param far the far frame of the frustum

• @param result the altered `Matrix4f`
*/
public static void matrixProjection( float fovY, float aspect, float near,
float far, Matrix4f result ) {

camera.setFrustumPerspective( fovY, aspect, near, far );

result.set( camera.getProjectionMatrix() );
}

/**

• Populates a `Matrix4f` with the proper frustum transformations

• from the Projection matrix.

• @param frustumLeft the left plane of the frustum

• @param frustumRight the right plane of the frustum

• @param frustumBottom the bottom plane of the frustum

• @param frustumTop the top plane of the frustum

• @param frustumNear the near plane of the frustum

• @param frustumFar the far plane of the frustum

• @param result the altered `Matrix4f`
*/
public static void matrixFrustum( float frustumLeft, float frustumRight,
float frustumBottom, float frustumTop, float frustumNear,
float frustumFar, Matrix4f result ) {

camera.setFrustum( frustumFar, frustumFar, frustumLeft, frustumRight,
frustumTop, frustumFar );

result.set( camera.getProjectionMatrix() );
}

}

[/java]

[java]
/*

• 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 ocean;

import com.jme3.asset.AssetManager;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Plane;
import com.jme3.math.Ray;
import com.jme3.math.Vector3f;
import com.jme3.post.SceneProcessor;
import com.jme3.renderer.Camera;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.Renderer;
import com.jme3.renderer.ViewPort;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Spatial;
import com.jme3.texture.FrameBuffer;
import com.jme3.texture.Image.Format;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture2D;
import com.jme3.ui.Picture;
import com.jme3.water.ReflectionProcessor;

/**
*

• @author Matthias Schellhase (based on SimpleWaterProcessor)
*/
public class ProjectedWaterProcessorWithRefraction implements SceneProcessor {

protected RenderManager rm;
protected ViewPort vp;
protected Spatial reflectionScene;
protected ViewPort reflectionView;
protected ViewPort refractionView;
protected FrameBuffer reflectionBuffer;
protected FrameBuffer refractionBuffer;
protected Camera mainCamera;
protected Camera reflectionCam;
protected Camera refractionCam;
protected Texture2D reflectionTexture;
protected Texture2D refractionTexture;
protected Texture2D depthTexture;
protected Texture2D normalTexture;
protected Texture2D dudvTexture;
protected Texture2D foamTexture;
protected int renderWidth = 512;
protected int renderHeight = 512;
protected Plane plane = new Plane(Vector3f.UNIT_Y, Vector3f.ZERO.dot(Vector3f.UNIT_Y));
protected float speed = 0.05f;
protected Ray ray = new Ray();
protected Vector3f targetLocation = new Vector3f();
protected AssetManager manager;
protected Material material;
protected float waterDepth = 1; // used ?
protected float waterTransparency = 0.4f; // used ?
protected boolean debug = false;
private Picture dispRefraction;
private Picture dispReflection;
private float speedReflection;

private Plane reflectionClipPlane;
private Plane refractionClipPlane;
private float refractionClippingOffset = 0f;
private float reflectionClippingOffset = 0f;
private Vector3f vect1 = new Vector3f();
private Vector3f vect2 = new Vector3f();
private Vector3f vect3 = new Vector3f();

public ProjectedWaterProcessorWithRefraction(Camera cam, AssetManager manager, float seaState) {
this.manager = manager;
mainCamera = cam;
material = new Material(manager, “Waterdata/ProjectedWaterWithRefraction.j3md”);
material.setVector3(“binormal”, new Vector3f(0.0f, 0.0f, -1.0f));
material.setVector3(“tangent”, new Vector3f(1.0f, 0.0f, 0.0f));
material.setBoolean(“abovewater”, true);
material.setColor(“waterColor”, new ColorRGBA( 0.0f, 0.0f, 0.1f, 1.0f ));
material.setColor(“waterColorEnd”, new ColorRGBA( 0.0f, 0.3f, 0.1f, 1.0f ));
material.setColor(“fogColor”, new ColorRGBA(1.0f, 1.0f, 1.0f, 0.1f));
material.setFloat(“fogStart”, 1.0f);
material.setFloat(“fogScale”, 0.001f);
material.setFloat(“amplitude”, 0.01f*seaState); // Smaller values show the foam earlier.
material.setFloat(“heightFalloffStart”, 300.0f);
material.setFloat(“heightFalloffSpeed”, 500.0f);

`````` updateClipPlanes();
``````

}

public void initialize(RenderManager rm, ViewPort vp) {
this.rm = rm;
this.vp = vp;

`````` loadTextures(manager);
createTextures();
applyTextures(material);

createPreViews();

if (debug) {
dispRefraction = new Picture("dispRefraction");
dispRefraction.setTexture(manager, refractionTexture, false);
dispReflection = new Picture("dispRefraction");
dispReflection.setTexture(manager, reflectionTexture, false);
}
``````

}

`````` normalTexture.setWrap(Texture.WrapMode.MirroredRepeat);
dudvTexture.setWrap(Texture.WrapMode.Repeat);
foamTexture.setWrap(Texture.WrapMode.Repeat);
``````

}

protected void createTextures() {
reflectionTexture = new Texture2D(renderWidth, renderHeight, Format.RGBA8);
refractionTexture = new Texture2D(renderWidth, renderHeight, Format.RGBA8);
reflectionTexture.setWrap(Texture.WrapMode.Repeat);
depthTexture = new Texture2D(renderWidth, renderHeight, Format.Depth);
}

protected void applyTextures(Material mat) {
mat.setTexture(“reflection”, reflectionTexture);
mat.setTexture(“refraction”, refractionTexture);
mat.setTexture(“normalMap”, normalTexture);
mat.setTexture(“dudvMap”, dudvTexture);
mat.setTexture(“foamMap”, foamTexture);
mat.setTexture(“depthMap”, depthTexture);
}

protected void createPreViews() {
reflectionCam = new Camera(renderWidth, renderHeight);
refractionCam = new Camera(renderWidth, renderHeight);

`````` // create a pre-view. a view that is rendered before the main view
reflectionView = new ViewPort("Reflection View", reflectionCam);
reflectionView.setClearFlags(true, true, true);
reflectionView.setBackgroundColor(ColorRGBA.Black);
// create offscreen framebuffer
reflectionBuffer = new FrameBuffer(renderWidth, renderHeight, 1);
//setup framebuffer to use texture
reflectionBuffer.setDepthBuffer(Format.Depth);
reflectionBuffer.setColorTexture(reflectionTexture);

//set viewport to render to offscreen framebuffer
reflectionView.setOutputFrameBuffer(reflectionBuffer);
// attach the scene to the viewport to be rendered
reflectionView.attachScene(reflectionScene);

// create a pre-view. a view that is rendered before the main view
refractionView = new ViewPort("Refraction View", refractionCam);
refractionView.setClearFlags(true, true, true);
refractionView.setBackgroundColor(ColorRGBA.Black);
// create offscreen framebuffer
refractionBuffer = new FrameBuffer(renderWidth, renderHeight, 1);
//setup framebuffer to use texture
refractionBuffer.setDepthBuffer(Format.Depth);
refractionBuffer.setColorTexture(refractionTexture);
refractionBuffer.setDepthTexture(depthTexture);
//set viewport to render to offscreen framebuffer
refractionView.setOutputFrameBuffer(refractionBuffer);
// attach the scene to the viewport to be rendered
refractionView.attachScene(reflectionScene);
``````

}

public void reshape(ViewPort vp, int w, int h) {
}

public boolean isInitialized() {
return rm != null;
}
float time = 0;
float savedTpf = 0;

public void preFrame(float tpf) {
time = time + (tpf * speed);
if (time > 1f) {
time = 0;
}
material.setFloat(“normalTranslation”, speedReflection * time);
material.setVector3(“cameraPos”, mainCamera.getLocation());

`````` savedTpf = tpf;
``````

}

public void setReflectionScene(Spatial spat) {
reflectionScene = spat;
}

public Material getMaterial() {
return material;
}

public void setDebug(boolean debug) {
this.debug = debug;
}

private void updateClipPlanes() {
reflectionClipPlane = plane.clone();
reflectionClipPlane.setConstant(reflectionClipPlane.getConstant() + reflectionClippingOffset);
refractionClipPlane = plane.clone();
refractionClipPlane.setConstant(refractionClipPlane.getConstant() + refractionClippingOffset);
}

public void postQueue(RenderQueue rq) {
Camera sceneCam = rm.getCurrentCamera();

`````` //update ray
ray.setOrigin(sceneCam.getLocation());
ray.setDirection(sceneCam.getDirection());

//update refraction cam
refractionCam.setLocation(sceneCam.getLocation());
refractionCam.setRotation(sceneCam.getRotation());
refractionCam.setFrustum(sceneCam.getFrustumNear(),
sceneCam.getFrustumFar(),
sceneCam.getFrustumLeft(),
sceneCam.getFrustumRight(),
sceneCam.getFrustumTop(),
sceneCam.getFrustumBottom());

//update reflection cam
boolean inv = false;
if (!ray.intersectsWherePlane(plane, targetLocation)) {
ray.setDirection(ray.getDirection().negateLocal());
ray.intersectsWherePlane(plane, targetLocation);
inv = true;
}
Vector3f loc = plane.reflect(sceneCam.getLocation(), new Vector3f());
reflectionCam.setLocation(loc);
reflectionCam.setFrustum(sceneCam.getFrustumNear(),
sceneCam.getFrustumFar(),
sceneCam.getFrustumLeft(),
sceneCam.getFrustumRight(),
sceneCam.getFrustumTop(),
sceneCam.getFrustumBottom());
// tempVec and calcVect are just temporary vector3f objects
float planeDistance = plane.pseudoDistance(vect1);
vect2.set(plane.getNormal()).multLocal(planeDistance * 2.0f);
vect3.set(vect1.subtractLocal(vect2)).subtractLocal(loc).normalizeLocal().negateLocal();
// now set the up vector
reflectionCam.lookAt(targetLocation, vect3);
if (inv) {
reflectionCam.setAxes(reflectionCam.getLeft().negateLocal(), reflectionCam.getUp(), reflectionCam.getDirection().negateLocal());
}

//Rendering reflection and refraction
rm.renderViewPort(reflectionView, savedTpf);
rm.renderViewPort(refractionView, savedTpf);
rm.getRenderer().setFrameBuffer(vp.getOutputFrameBuffer());
rm.setCamera(sceneCam, false);
``````

}

public void postFrame(FrameBuffer out) {
if (debug) {
displayMap(rm.getRenderer(), dispRefraction, 64);
displayMap(rm.getRenderer(), dispReflection, 256);
}
}

public void cleanup() {
}

//debug only : displays maps
protected void displayMap(Renderer r, Picture pic, int left) {
Camera cam = vp.getCamera();
rm.setCamera(cam, true);
int h = cam.getHeight();

`````` pic.setPosition(left, h / 20f);

pic.setWidth(128);
pic.setHeight(128);
pic.updateGeometricState();
rm.renderGeometry(pic);
rm.setCamera(cam, false);
``````

}

/**

• Refraction Processor
*/
public class RefractionProcessor implements SceneProcessor {

RenderManager rm;
ViewPort vp;

public void initialize(RenderManager rm, ViewPort vp) {
this.rm = rm;
this.vp = vp;
}

public void reshape(ViewPort vp, int w, int h) {
}

public boolean isInitialized() {
return rm != null;
}

public void preFrame(float tpf) {
refractionCam.setClipPlane(refractionClipPlane, Plane.Side.Negative);//,-1

}

public void postQueue(RenderQueue rq) {
}

public void postFrame(FrameBuffer out) {
}

public void cleanup() {
}
}
}

[/java]

[java]
/*

• 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 ocean;

import com.jme3.math.FastMath;
import com.jme3.terrain.noise.basis.ImprovedNoise;

/**

• `WaterHeightGenerator`

• Sample implementation of a water height generator

• @author Rikard Herlitz (MrCoder)
*/
public class WaterHeightGenerator implements HeightGenerator
{
private float myScale;
private float scalexsmall;
private float scaleysmall;
private float scalexbig;
private float scaleybig;
private float heightsmall;
private float heightbig;
private float speedsmall;
private float speedbig;
private int octaves;

public WaterHeightGenerator (int seastate, float scale)
{
myScale = scale;
setSeaState(seastate);
}

`````` public float getHeight( float x, float z, float time ) {
float zval = z * scaleybig * 4f + time * speedbig * 4f;
float height = FastMath.sin( zval );
height *= heightbig;

if( octaves &gt; 0 ) {
float height2 = (float) ImprovedNoise.noise( x * scaleybig, z * scalexbig, time * speedbig ) * heightbig;
height = height * 0.4f + height2 * 0.6f;
}
if( octaves &gt; 1 )
{
height += ImprovedNoise.noise( x * scaleysmall, z * scalexsmall, time * speedsmall ) * heightsmall;
}
if( octaves &gt; 2 )
{
height += ImprovedNoise.noise( x * scaleysmall * 2.0f, z * scalexsmall * 2.0f, time * speedsmall * 1.5f ) * heightsmall * 0.5f;
}
if( octaves &gt; 3 )
{
height += ImprovedNoise.noise( x * scaleysmall * 4.0f, z * scalexsmall * 4.0f, time * speedsmall * 2.0f ) * heightsmall * 0.25f;

}
return height;
``````

}

public float getScalexsmall() {
return scalexsmall;
}

public void setScalexsmall( float scalexsmall ) {
this.scalexsmall = scalexsmall;
}

public float getScaleysmall() {
return scaleysmall;
}

public void setScaleysmall( float scaleysmall ) {
this.scaleysmall = scaleysmall;
}

public float getScalexbig() {
return scalexbig;
}

public void setScalexbig( float scalexbig ) {
this.scalexbig = scalexbig;
}

public float getScaleybig() {
return scaleybig;
}

public void setScaleybig( float scaleybig ) {
this.scaleybig = scaleybig;
}

public float getHeightsmall() {
return heightsmall;
}

public void setHeightsmall( float heightsmall ) {
this.heightsmall = heightsmall;
}

public float getHeightbig() {
return heightbig;
}

public void setHeightbig( float heightbig ) {
this.heightbig = heightbig;
}

public float getSpeedsmall() {
return speedsmall;
}

public void setSpeedsmall( float speedsmall ) {
this.speedsmall = speedsmall;
}

public float getSpeedbig() {
return speedbig;
}

public void setSpeedbig( float speedbig ) {
this.speedbig = speedbig;
}

public int getOctaves() {
return octaves;
}

public void setOctaves( int octaves ) {
this.octaves = octaves;
}

`````` public void setSeaState (int state)
{
System.out.println(state);
scalexsmall = 5f/(1+state);
scaleysmall = 4f/(1+state);
scalexbig = 1.5f/(1+state);
scaleybig = 0.6f/(1+state);
heightsmall = 0.05f*state;
heightbig = 0.01f+0.1f*state;
speedsmall = 0.4f;
speedbig = 0.2f;
octaves = 2;//6-(state/4);

}
``````

}

[/java]

[java]
package oceanbuoyancy;

import com.jme3.bounding.BoundingBox;
import com.jme3.bullet.PhysicsSpace;
import com.jme3.bullet.PhysicsTickListener;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.collision.CollisionResults;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Ray;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.AbstractControl;
import com.jme3.scene.control.Control;
import com.jme3.scene.shape.Sphere;

/**
*

• @author Arjen
*/
public final class BuoyancyControl extends AbstractControl implements PhysicsTickListener
{
boolean showControlPoints = true; // If set to ‘true’, the control points will be visualized with spheres.

Ship myShip;
Node myHull;
float myMass;
float myDraft;
RigidBodyControl myShipsPhysics;

int qtyCP = 50; // Number of contol points to be used alongside the entire hull (half of them on starboard side , half of them on port side)

Vector3f[] vecControlPoint = new Vector3f[qtyCP]; // Position of control point relative to the ship.
Vector3f[] vecControlPointCalc = new Vector3f[qtyCP]; // Position of control point in world coordinates (for applying forces).
Quaternion shipQuat = new Quaternion(Quaternion.ZERO);
Vector3f shipPos = new Vector3f();

float[] waveHeight = new float[qtyCP];
float[] cpHeight = new float[qtyCP];

// Strength of the wave at every cross-section of the ship (displacement)
float[] localDisplacement = new float[qtyCP];

Vector3f waterImpulseForce = new Vector3f(), waterImpulseLocation = new Vector3f();
Vector3f gravityImpulseForce = new Vector3f(), gravityImpulseLocation = new Vector3f();

float waveForce; // Force applied to follow waves.

Sphere[] buoySphere = new Sphere[qtyCP];
Geometry[] buoyGeo = new Geometry[qtyCP];
Material[] matBuoy = new Material[qtyCP];

/**

*/
public BuoyancyControl()
{
}

/**

• Adds a BuoyancyControl to the ship object.

• @param ship The ship class object in which the ship is assembled.

• @param myPhysic The ships RigidBodyControl.

• @return none
/
public BuoyancyControl(Ship ship, RigidBodyControl myPhysic)
{
myShip = ship;
myHull = myShip.nodeHull;
myShipsPhysics = myPhysic;//myShip.getControl(RigidBodyControl.class);
myMass = myShipsPhysics.getMass();
myDraft = myShip.shipsDraft;
waveForce = myMass
myShip.gravity;

for (int i = 0; i<qtyCP; i++)
{
vecControlPoint[i] = new Vector3f(Vector3f.ZERO);
}

createControlPoints();

}

public void prePhysicsTick(PhysicsSpace space, float tpf)
{
checkWavePos();
}

public void physicsTick(PhysicsSpace space, float tpf)
{
followWave(tpf);
}

/**

• Here we are creating the positions of the control points which we use to calculate the depth of the hull in relation to the water.
*/
public void createControlPoints()
{
float myLength = ((BoundingBox)myHull.getWorldBound()).getXExtent(); // Lenght of the hull (half of its lenght).

float xCoor; // X-coordinate for the control point
float zCoor; // Z-coordinate for the control point

for (int i=0; i<qtyCP/2; i++)
{
xCoor = (-myLength) + i*(2fmyLength / (0.5fqtyCP-1f)); // Create X-coordinates over the lenght of the hull, evenly spaced.

`````` Ray ray = new Ray(new Vector3f(xCoor, 0f, 0f), new Vector3f(0f, 0f, 1f));  //  Create a ray from the xCoor to the side of teh hull to check the width of the hull at that cross-section.
CollisionResults results = new CollisionResults();
myHull.collideWith(ray, results);
if (results.size() &gt; 0)
{
zCoor = results.getCollision(0).getDistance();  //  Define the Z-coordinate for this cross-section.
}
else
{
zCoor = 0f;  //  To prevent 'out of bound array error' when there is no collision (especially at stern and bow, because mostly the waterline is shorter then the overal lenght of the hull).
}

if (i&lt;qtyCP/4)
{
localDisplacement[i] = 10f*(i+1)*(0.5f/qtyCP/2)/(qtyCP/4);
localDisplacement[i+(qtyCP/2)] = localDisplacement[i];

localDisplacement[(qtyCP/2)-1-i] = localDisplacement[i];
localDisplacement[qtyCP-1-i] = localDisplacement[i];
}

vecControlPoint[i].set(xCoor, 0f, zCoor);  //  Set the coordinates for the given control point on starboard side.
vecControlPoint[i+qtyCP/2].set(xCoor, 0f, -zCoor);  //  Set the coordinates for the given control point on port side.

vecControlPointCalc[i] = new Vector3f(xCoor, 0f, zCoor);  //  Set the coordinates for the given calculated control point on starboard side.
vecControlPointCalc[i+qtyCP/2] = new Vector3f(xCoor, 0f, -zCoor);  //  Set the coordinates for the given calculated control point on port side.

matBuoy[i].setColor("Color", new ColorRGBA(1f, 1f, 0f, 1f));
matBuoy[i+qtyCP/2].setColor("Color", new ColorRGBA(1f, 1f, 1f, 1f));

if (showControlPoints)
{
buoySphere[i] = new Sphere(5, 5, 0.1f);
buoySphere[i+qtyCP/2] = new Sphere(5, 5, 0.1f);
buoyGeo[i] = new Geometry("buoyGeo[" + i + "]", buoySphere[i]);
buoyGeo[i+qtyCP/2] = new Geometry("buoyGeo[" + (i+qtyCP/2) + "]", buoySphere[i]);
buoyGeo[i].setMaterial(matBuoy[i]);
buoyGeo[i+qtyCP/2].setMaterial(matBuoy[i]);
buoyGeo[i].setLocalTranslation(vecControlPointCalc[i]);
buoyGeo[i+qtyCP/2].setLocalTranslation(vecControlPointCalc[i+qtyCP/2]);
myHull.attachChild(buoyGeo[i]);
myHull.attachChild(buoyGeo[i+qtyCP/2]);
}
``````

}

for (int i=0; i<qtyCP; i++)
{
waveHeight[i] = 0f; // Fill/create the wave height values.
cpHeight[i] = 0f; // Fill/create the control point height values.
}

}

/**

• Check the height of the waves for each control point location.
*/
static Vector3f vec1 = new Vector3f(); // scratch space variable
public void checkWavePos()
{
shipQuat.set(myShipsPhysics.getPhysicsRotation()); // Store the physic body’s rotation.
shipPos.set(myShipsPhysics.getPhysicsLocation()); // Store the physic body’s location

// For every control point, calculate the world location, then get the height of the wave at that world location by accessing the waterheight generator, then set the control point height of that location:
for (int i=0; i<qtyCP; i++)
{
// new:
shipQuat.multLocal(vec1.set(vecControlPoint[i]));
// end new

`````` //vecControlPointCalc[i] = shipPos.add(shipQuat.mult(vecControlPoint[i]).mult(myScale));
waveHeight[i] = myShip.myGame.whGEN.getHeight(vecControlPointCalc[i].x, vecControlPointCalc[i].z, myShip.myGame.grid.myTime);
cpHeight[i] = vecControlPointCalc[i].y;
``````

}
}

/**

• Apply the forces which make the hull follow the waves:
*/
public void followWave(float tpf)
{
shipPos.set(myShipsPhysics.getPhysicsLocation()); // Store the physic body’s location

// Process every control point:
for (int i=0; i<qtyCP; i++)
{
waterImpulseForce.set(0f, (localDisplacement[i]waveForce(waveHeight[i]-cpHeight[i])/myDraft)*tpf, 0); // Set the force.
waterImpulseLocation.set(vecControlPointCalc[i].subtract(shipPos)); // Set the location the force has to be applied to.
myShipsPhysics.applyImpulse(waterImpulseForce, waterImpulseLocation); // Apply the force to the hull.

`````` if (showControlPoints)
{
matBuoy[i].setColor("Color", new ColorRGBA(10f*(waveHeight[i]-cpHeight[i])/myDraft, 10f*(cpHeight[i]-waveHeight[i])/myDraft, 1f, 1f));
}
``````

}
}

public float getSpeedY() {
return myShipsPhysics.getLinearVelocity().y;
}

@Override
protected void controlUpdate(float tpf)
{
}

@Override
protected void controlRender(RenderManager rm, ViewPort vp)
{
//throw new UnsupportedOperationException(“Not supported yet.”);
}

public Control cloneForSpatial(Spatial spatial)
{
final BuoyancyControl myControl = new BuoyancyControl();
return myControl;
}
}

[/java]

[java]
package oceanbuoyancy;

import com.jme3.app.SimpleApplication;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.PhysicsSpace;
import com.jme3.bullet.PhysicsTickListener;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.AnalogListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Box;
import com.jme3.system.AppSettings;
import com.jme3.texture.Texture;
import com.jme3.util.SkyFactory;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.vecmath.Point3f;
import ocean.MyProjectedGrid;
import ocean.ProjectedWaterProcessorWithRefraction;
import ocean.WaterHeightGenerator;

public class Main extends SimpleApplication implements PhysicsTickListener
{
BulletAppState bulletAppState;

``````float gravity = 9.81f;
float gameScale = 0.1f;

public static void main(String[] args) throws IOException
{
Main app = new Main();

AppSettings settings = new AppSettings(true);
app.setSettings(settings);

Logger.getLogger("").setLevel(Level.SEVERE);  //  Disable most output in the logger.
//app.showSettings = false;  //  Remove the Display Settings Screen.
//app.setDisplayFps(false);
//app.setDisplayStatView(true);

app.start();
}

@Override
public void simpleInitApp()
{

bulletAppState = new BulletAppState();
stateManager.attach(bulletAppState);
bulletAppState.getPhysicsSpace().setMaxSubSteps(2);  //  Compensate for low framerates by making this value '2'.
bulletAppState.getPhysicsSpace().setGravity(new Vector3f(0f,0f,0f));
bulletAppState.setEnabled(true);
bulletAppState.setSpeed(1f);

//  Make collision shapes visible:
//bulletAppState.getPhysicsSpace().enableDebug(assetManager);

rootNode.setCullHint(Spatial.CullHint.Never);

cam.setLocation(new Vector3f(0f, 3f, 10f));
flyCam.setMoveSpeed(5f);

createScene();
initKeys();
}

MyProjectedGrid grid;
Geometry projectedGridGeometry;
ProjectedWaterProcessorWithRefraction waterProcessor;
WaterHeightGenerator whGEN;
int seaState = 4;  //  State of the ocean (0 = no waves, 8 is storm).
Node sceneNode = new Node("Scene");
Ship playerShip;
public void createScene()
{
playerShip = new Ship(this, renderManager, sceneNode, new Point3f(0f, 0f, 0f), 45);

whGEN = new WaterHeightGenerator(seaState, gameScale);
grid = new MyProjectedGrid(getTimer(), getCamera(), (int)(100), (int)(100), 0.05f, whGEN);
projectedGridGeometry = new Geometry("Projected Grid", grid);  // create cube geometry from the shape
projectedGridGeometry.setCullHint(Spatial.CullHint.Never);
projectedGridGeometry.setMaterial(setWaterProcessor());
projectedGridGeometry.setLocalTranslation(0, 0, 0);
rootNode.attachChild(projectedGridGeometry);

// A white, directional light source
DirectionalLight sun = new DirectionalLight();
sun.setDirection(new Vector3f(-0.1f, -1.0f, 1.0f).normalizeLocal());
sun.setColor(ColorRGBA.White.mult(1.0f));

AmbientLight al = new AmbientLight();
al.setColor(ColorRGBA.White.mult(2.5f));

//Create a box for testing:
Box testBox = new Box(new Vector3f(0f,0f,-5f), 0.5f, 5f, 0.5f);
Geometry geoTest = new Geometry("Box", testBox);
Material matTest = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
matTest.setColor("Color", ColorRGBA.Red);
geoTest.setMaterial(matTest);
sceneNode.attachChild(geoTest);  // Only nodes attached to the sceneNode will be reflected in the water.

}

private Material setWaterProcessor()
{
waterProcessor = new ProjectedWaterProcessorWithRefraction(cam,assetManager, seaState);
waterProcessor.setReflectionScene(sceneNode);
waterProcessor.setDebug(false);
return waterProcessor.getMaterial();
}

String dir = "Textures/Sky/skybox/";
Texture north = assetManager.loadTexture(dir + "1.jpg");
Texture south = assetManager.loadTexture(dir + "3.jpg");
Texture east = assetManager.loadTexture(dir + "2.jpg");
Texture west = assetManager.loadTexture(dir + "4.jpg");
Texture up = assetManager.loadTexture(dir + "6.jpg");
Texture down = assetManager.loadTexture(dir + "5.jpg");

Spatial sky = SkyFactory.createSky(assetManager, west, east, north, south, up, down);
sky.scale(30000f);

sceneNode.attachChild(sky);
rootNode.attachChild(sceneNode);
}

@Override
public void simpleUpdate(float tpf)
{
grid.update(cam.getViewMatrix().clone());  //  Update the waves.
}

@Override
public void simpleRender(RenderManager rm)
{
}

public void prePhysicsTick(PhysicsSpace space, float f)
{
}

public void physicsTick(PhysicsSpace space, float f)
{
}

private void initKeys()
{

{
"Sea State Up",
"Sea State Down"
});

{
});
``````

}

``````private ActionListener actionListener = new ActionListener()
{
public void onAction(String name, boolean keyPressed, float tpf)
{
if (name.equals("Sea State Up") &amp;&amp; !keyPressed)
{
if (seaState &lt; 8)
{
seaState ++;
grid.myHeightGenerator.setSeaState(seaState);
}
}
if (name.equals("Sea State Down")  &amp;&amp; !keyPressed)
{
if (seaState &gt; 1)
{
seaState --;
grid.myHeightGenerator.setSeaState(seaState);
}
}
}
};

private AnalogListener analogListener = new AnalogListener()
{
public void onAnalog(String name, float value, float tpf)
{
}
};
``````

}

[/java]

[java]
package oceanbuoyancy;

import com.jme3.asset.AssetManager;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import formulas.Formulas;
import javax.vecmath.Point3f;

public final class Ship extends Node
{
Main myGame; //Reference to the main game.
AssetManager myAssetManager;
Node myScene;
BulletAppState myBulletAppState;
RenderManager myRenderManager;

``````Node nodeHull, myModel;
Geometry geoHull;

float shipsDraft = 3.816f;

Point3f shipPos = new Point3f(0.0f, 0.0f, 0.0f);  //  Position of the ship (has to be passed by the main game).

float shipsCourse;

RigidBodyControl shipPhysicsControl = new RigidBodyControl();
BuoyancyControl buoyancyControl = new BuoyancyControl();

float shipsMass = 2500f;
float gravity;
Vector3f vecGravity;

Formulas formulas = new Formulas();

/**
* Adds a ship to the scene.
*
* @param mn reference to the main game
* @param rendermng reference to the main games render manager
* @param scene reference to the node the ship has to be attached to. If this is 'sceneNode', the ships reflection will be shown.
* @param pos Point3f holding the ships initial position
* @return none
*/
public Ship(Main mn, RenderManager rendermng, Node scene, Point3f pos, float head)
{
myGame = mn;
myAssetManager = myGame.getAssetManager();//assetManager;
myRenderManager = rendermng;
myScene = scene;
myBulletAppState = myGame.getStateManager().getState(BulletAppState.class);
shipPos = pos;
gravity = myGame.gravity;
vecGravity = new Vector3f(0f, -gravity, 0f);

assembleShip();
}

public void assembleShip()
{

nodeHull = (Node)myModel.getChild("Hull");
//geoHull = ((Geometry) ((Node) nodeHull).getChild("Hull1"));

//HullCollisionShape colShape = new HullCollisionShape(geoHull.getMesh());
//shipPhysicsControl = new RigidBodyControl(colShape, shipsMass);
shipPhysicsControl = new RigidBodyControl(shipsMass);

shipPhysicsControl.setGravity(new Vector3f(0f, 0f, 0f));

buoyancyControl = new BuoyancyControl(this, shipPhysicsControl);

nodeHull.setLocalTranslation(new Vector3f(0,0,0));

shipPhysicsControl.setAngularDamping(0.5f);
shipPhysicsControl.setLinearDamping(0.8f);

myScene.attachChild(nodeHull);

//  Set the ships initial position:
shipPhysicsControl.setPhysicsRotation(new Quaternion().fromAngleAxis(shipsCourse, new Vector3f(0,1,0)));
shipPhysicsControl.setPhysicsLocation(new Vector3f(shipPos.x, shipPos.y, shipPos.z));
}
``````

}

[/java]

[java]

/*

• To change this template, choose Tools | Templates
• and open the template in the editor.
*/
package oceanbuoyancy;

import com.jme3.asset.AssetInfo;
import java.io.IOException;
import java.util.Scanner;

/**
*

• @author Arjen
*/

{
public Object load(AssetInfo assetInfo) throws IOException
{
Scanner scan = new Scanner(assetInfo.openStream());
StringBuilder sb = new StringBuilder();
try
{
while (scan.hasNextLine())
{
sb.append(scan.nextLine()).append("/n");
}
}
finally
{
scan.close();
}
return sb.toString();
}
}
[/java]

Do you @husky or anybody else know why this code doesn’t run on android? It runs fine on windows but JMonkey doesn’t create the android build.

I tried creating a new project and copied over the assets and classes, and JMonkey doesn’t create an android build. I’m just wondering. :-?

I’ll keep working on the code to get it working but if any of you already know the answer feel free to post it here.

EDIT: After importing the javax.vecmath library it gives me an error related to the shaders. Since I don’t know anything about shaders I deleted the shaders. Now I have white waves with no reflection, which looks like a pure white milk ocean. I’m going to replace all the deleted code with the simple wave reflection code from the sample tests and that should fix everything.

1 Like

@pixelapp
If you don’t want reflection, you can also jut create a material for the water surface with a texture. Don’t forget to remove the ‘setWaterProcessor Material’ if you decide to do so! The reflection also has an impact on the frame rate, so it might be wise to remove it when required.

@pixelapp: If you have a dependency on javax.vecmath you use jbullet with is butt slow on android. The SDK normally replaces the bullet libraries with the native ones so I take it you don’t use it. Then use the native bullet library and according android binary libraries from the opt folder and remove the bullet libraries (see the wiki for the function of the single jars)