Hey, just wanted to give a little back for all the wonderful work that’s gone into the JME3 engine. When we release, we’re going to provide our modifications to the JME3 engine. But, for right now, would y’all like some multi-core accelerated fluid dynamics?
With 1000 vortons and 4000 tracer particles (this looks kinda okay, if you fiddle with distribution enough), I’m getting approximately 200fps. Bump it up to 6000 vortons and 10000 tracers (which looks boss as ****), and it’ll do roughly 20-30fps
They’re definitely not something that you should use to design an airplane wing. And it’s still really, really rough. But, y’all might like it.
cool thx :D!
Awesome! Any demo videos online somewhere?
Excellent! People will find this quite useful.
Do you have a video or any pictures of it in action?
Sorry, I don’t have any video.
I develop on linux, and the vidcap tools here are not at all friendly with openGL from Java. Segfault city, man.
However, there is a self-contained demo in the repo (TestFluid.java) that creates a SimpleApplication showing a single mushroom cloud. There are no dependencies other than jme3.
Fiddle with the first arguments of the VortonSpace constructor and FluidView constructors to alter the number of vortons (simulation particles) and tracers (visual particles). nVortons should be a perfect cube: 1000, 1331, 6859, etc. It won’t blow up if it isn’t, but the distribution will be asymetrical and cause artifacts (that you may actually like).
I’ll be documenting the code for the next couple of hours, so the parameters should be better defined soon.
Cool, got it working. Looks fun and quite realistic.
I will try and capture a video today and post here.
Sweet! Thank you!
Alright, it’s all javadoc’d and slightly decrufted.
Cool, thanks for this!
@Sploreg
Hope you find the time for making a quick preview vid - Im too lazy/busy to look at the code etc.
I hope, its what I think it is!
It is what you think it is, staugaard.
It’s fluid dynamics. Swirly smoke, chaotic explosions, high cpu cost, the works.
@Netzapper said:
Sorry, I don't have any video.
I develop on linux, and the vidcap tools here are *not* at all friendly with openGL from Java. Segfault city, man.
I use *Record It Now* and it works like a boss. But it just work accelerated if you setup it right. I use the poor Mesa driver, and still I'm able to record videos like a charm :).
@glaucomardano said:
I use *Record It Now* and it works like a boss. But it just work accelerated if you setup it right. I use the poor Mesa driver, and still I'm able to record videos like a charm :).
Ah! This works great, actually.
Here's a youtube demo. The quality is not wonderful, but the only thing really on display is the motion of the particles: http://youtu.be/eouA0nb83Rw
WOw. Really great man :). What parts of the engine did you changed?
Couldn’t this be made a particle influencer?
For this particular module, no engine changes are necessary. This should work with an unmodified JME.
I haven’t included materials in the distribution, though, so you may like to write some. Also, the geometry for the tracer particles uses point sprites, so you might want to amend that for general compatibility. I’ll copypasta my material shaders at the bottom of here, though.
As for modifications to the engine for our game:
- Better stencil buffer support in materials (including setting all stencil op parameters and reference values)
- Selectable depth testing functions (I think I added this… if not, my apologies for thinking I did work I didn’t)
- Expanded default parameter types available in materials
- Added GL line smoothing flag
- Geometry shader support
- Convenience methods in math classes (specifically, a couple additions to Vec3 and Quat)
- Major addition to particle system with ParticleAffector, which allows for programmable influence to particles throughout their lifetime (not just at spawn)
- SceneProcessor filters support DepthTexture outputs
There are also a couple of other modifications we’ve made that are of literally no interest to anybody else. In fact, they’d probably be considered bugs in a general release.
“But Netzapper”, I hear you say, “that all sounds great! Why haven’t you submitted any of your patches?”
And the answer is: honestly, much of this stuff doesn’t fit the design goals of JME3 with regards to compatibility. I don’t have fallback schemes for failing to have the required capabilities. Our game’s minsysreqs are OpenGL 3.2 (which actually hits the vast majority of cards out there). Furthermore, I just don’t have time to shepherd each patch through the acceptance process right now, especially if doing so requires writing support for older cards (that we aren’t targeting). I’d much rather wait until we’ve launched, and then work with y’all to port appropriate features into the mainline JME3.
Fluid material:
[java]
//Fluid.frag
#ifdef USE_TEXTURE
uniform sampler2D m_Texture;
#endif
uniform vec4 m_StartColor;
uniform vec4 m_EndColor;
in vec3 vars;
#ifdef USE_TEXTURE
void main(){
float ageFract = vars.x / vars.y;
vec4 ageColor = mix(m_StartColor, m_EndColor, ageFract);
vec2 uv = gl_PointCoord.xy;
vec4 texColor = texture2D(m_Texture, uv);
#ifdef PURE_ALPHA_MAP
ageColor.a *= texColor.r;
gl_FragColor = ageColor;
#else
gl_FragColor = texColor * ageColor;
#endif
}
#else //just color
void main(){
float ageFract = vars.x / vars.y;
gl_FragColor = mix(m_StartColor, m_EndColor, ageFract);
}
#endif //USE_TEXTURE[/java]
[java]
//Fluid.vert
uniform mat4 g_WorldViewProjectionMatrix;
uniform float m_StartSize;
uniform float m_EndSize;
attribute vec3 inPosition;
attribute vec3 inBindPosePosition;
out vec3 vars;
void main(){
vec4 pos = vec4(inPosition, 1.0);
vars = inBindPosePosition;
float ageFract = vars.x / vars.y;
gl_PointSize = mix(m_StartSize, m_EndSize, ageFract);
gl_Position = g_WorldViewProjectionMatrix * pos;
}[/java]
[java]
MaterialDef Fluid {
MaterialParameters {
Texture2D Texture
Boolean AlphaMap
Color StartColor
Color EndColor
Float StartSize
Float EndSize
}
Technique {
VertexShader GLSL150: materials/vertex/Fluid.vert
FragmentShader GLSL150: materials/fragment/Fluid.frag
WorldParameters {
WorldViewProjectionMatrix
}
Defines {
USE_TEXTURE : Texture
PURE_ALPHA_MAP : AlphaMap
}
RenderState {
PointSprite On
}
}
}
[/java]
@normen said:
Couldn't this be made a particle influencer?
Not in any way that I can see, unless ParticleInfluencer has majorly changed since I forked y'all.
The particles must be *constantly* updated with new velocities from the simulation, and last time I looked at mainline jme3 ParticleInfluencer only has an opportunity to affect the particle at spawn time.
Furthermore, advecting (moving) the tracer particles is multithreaded in my system. This is damn-near mandatory, since moving those particles consumes much of the work of the simulation. Making it part of the existing particle system would require rewriting ParticleEmitter to be multithreaded. This is *not* a straight forward task if you want to keep the current semantics (I know, because I already tried to do it).
Alright, I don’t think the threading would be an issue really, many parts of jme3 work threaded in the update loop but I didn’t look at the influencer interface much really, might be its underpowered for that, maybe we should improve it
I have an improvement available for that, actually. I added another interface called ParticleAffector whose job it is to affect particles after they’ve been created. I can offer you that patch but I don’t have the time right now to make modifications to it.
That said, to use the particle system with this basically means turning off everything that the particleemitter usually does. I prefer the lightweight purpose-built solution to loading up an existing system with switches and flags and whatnot to shoehorn in a different paradigm. Anyway, here’s my diff for com/jme3/effect/*.
[patch]diff --git a/src/core/com/jme3/effect/Particle.java b/src/core/com/jme3/effect/Particle.java
index 8e976f9…12de67e 100644
— a/src/core/com/jme3/effect/Particle.java
+++ b/src/core/com/jme3/effect/Particle.java
@@ -53,6 +53,23 @@ public class Particle {
public final Vector3f position = new Vector3f();
/**
-
* The initial position of this particle. This can be used<br />
-
* when velocity integration is disabled on the particle emitter<br />
-
* to do position(t) updates instead of velocity(t) updates.<br />
-
* */<br />
- public final Vector3f startPosition = new Vector3f();
+
- /**
-
* The current normal of the particle.<br />
-
* */<br />
- public final Vector3f normal = new Vector3f();
+
- /**
-
* Used to link user data to this particle.<br />
-
* */<br />
- public Object userData;
+
- /**
- Particle color
*/
public final ColorRGBA color = new ColorRGBA(0,0,0,0);
@@ -87,6 +104,8 @@ public class Particle {
*/
public int imageIndex = 0;
- public boolean marked = false;
+
/**
- Distance to camera. Only used for sorted particles.
*/
diff --git a/src/core/com/jme3/effect/ParticleAffector.java b/src/core/com/jme3/effect/ParticleAffector.java
new file mode 100644
index 0000000…eea035f
— /dev/null
+++ b/src/core/com/jme3/effect/ParticleAffector.java
@@ -0,0 +1,6 @@
+package com.jme3.effect;
+
+public interface ParticleAffector {
- public void affectParticle(float tpf, Particle particle);
- public void updateForFrame(float tpf);
+}
diff --git a/src/core/com/jme3/effect/ParticleEmitter.java b/src/core/com/jme3/effect/ParticleEmitter.java
index 9059ab5…ab347f3 100644
— a/src/core/com/jme3/effect/ParticleEmitter.java
+++ b/src/core/com/jme3/effect/ParticleEmitter.java
@@ -31,15 +31,17 @@
*/
package com.jme3.effect;
+import java.io.IOException;
+
import com.jme3.bounding.BoundingBox;
import com.jme3.effect.ParticleMesh.Type;
import com.jme3.effect.influencers.DefaultParticleInfluencer;
import com.jme3.effect.influencers.ParticleInfluencer;
import com.jme3.effect.shapes.EmitterPointShape;
import com.jme3.effect.shapes.EmitterShape;
+import com.jme3.export.InputCapsule;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
-import com.jme3.export.InputCapsule;
import com.jme3.export.OutputCapsule;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
@@ -54,7 +56,6 @@ import com.jme3.scene.Geometry;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.Control;
import com.jme3.util.TempVars;
-import java.io.IOException;
/**
- <code>ParticleEmitter</code> is a special kind of geometry which simulates
@@ -79,7 +80,8 @@ public class ParticleEmitter extends Geometry {
private EmitterShape shape = DEFAULT_SHAPE;
private ParticleMesh particleMesh;
private ParticleInfluencer particleInfluencer = DEFAULT_INFLUENCER;
- private ParticleMesh.Type meshType;
- private ParticleAffector particleAffector = null;
- private ParticleMesh.Type meshType;
private Particle[] particles;
private int firstUnUsed;
private int lastUsed;
@@ -105,6 +107,9 @@ public class ParticleEmitter extends Geometry {
private boolean worldSpace = true;
//variable that helps with computations
private transient Vector3f temp = new Vector3f();
- private int particleEmittedCount = 0;
- private int particleEmittedLimit = -1;
- private boolean enableVelocityIntegration = true;
private class ParticleEmitterControl implements Control {
@@ -197,6 +202,18 @@ public class ParticleEmitter extends Geometry {
public ParticleEmitter() {
super();
}
+
- public void enableVelocityIntegration(boolean enable){
-
enableVelocityIntegration = enable;<br />
- }
+
- public boolean isEnableVelocityIntegration(){
-
return enableVelocityIntegration;<br />
- }
+
- public void setEmitterLimit(int limit){
-
particleEmittedLimit = limit;<br />
- }
public void setShape(EmitterShape shape) {
this.shape = shape;
@@ -799,8 +816,18 @@ public class ParticleEmitter extends Geometry {
if (idx >= particles.length) {
return false;
}
+
-
if (!shape.isReady()){<br />
-
return false;<br />
-
}<br />
+
-
if (particleEmittedLimit >= 0 && particleEmittedCount >= particleEmittedLimit){<br />
-
return false;<br />
-
}<br />
-
particleEmittedCount++;<br />
Particle p = particles[idx];
+ p.marked = false;
if (selectRandomImage) {
p.imageIndex = FastMath.nextRandomInt(0, imagesY - 1) * imagesX + FastMath.nextRandomInt(0, imagesX - 1);
}
@@ -812,6 +839,7 @@ public class ParticleEmitter extends Geometry {
//shape.getRandomPoint(p.position);
particleInfluencer.influenceParticle(p, shape);
if (worldSpace) {
+ worldTransform.getRotation().multLocal(p.position);
p.position.addLocal(worldTransform.getTranslation());
}
if (randomAngle) {
@@ -821,6 +849,8 @@ public class ParticleEmitter extends Geometry {
p.rotateSpeed = rotateSpeed * (0.2f + (FastMath.nextRandomFloat() * 2f - 1f) * .8f);
}
+ p.startPosition.set(p.position);
+
temp.set(p.position).addLocal(p.size, p.size, p.size);
max.maxLocal(temp);
temp.set(p.position).subtractLocal(p.size, p.size, p.size);
@@ -831,11 +861,34 @@ public class ParticleEmitter extends Geometry {
return true;
}
+ public void resetLimitCount(){
+ this.particleEmittedCount = 0;
+ }
+
+ /**
+ * Emit a pulse of n particles at the current particle emission rate.
+ *
+ * Using this method modifies the emitter limit, and after the pulse
+ * no particles will be emitted until either another pulse is requested
+ * or the emitterLimit is set back to -1.
+ * */
+ public void pulseParticles(int n){
+ setEmitterLimit(n);
+ resetLimitCount();
+ }
+
/**
* Instantly emits all the particles possible to be emitted. Any particles
* which are currently inactive will be spawned immediately.
*/
public void emitAllParticles() {
+ emitParticles(particles.length);
+ }
+
+ /**
+ * Instantly emit n particles.
+ * */
+ public void emitParticles(int nParticles){
// Force world transform to update
this.getWorldTransform();
@@ -856,7 +909,9 @@ public class ParticleEmitter extends Geometry {
max.set(Vector3f.NEGATIVE_INFINITY);
}
- while (emitParticle(min, max));
+ for (int i = 0; i < nParticles && emitParticle(min, max); i++);
+
+ //while (emitParticle(min, max));
bbox.setMinMax(min, max);
this.setBoundRefresh();
@@ -929,18 +984,29 @@ public class ParticleEmitter extends Geometry {
// position += velocity * tpf
//p.distToCam = -1;
- // applying gravity
- p.velocity.x -= gravity.x * tpf;
- p.velocity.y -= gravity.y * tpf;
- p.velocity.z -= gravity.z * tpf;
- temp.set(p.velocity).multLocal(tpf);
- p.position.addLocal(temp);
-
// affecting color, size and angle
float b = (p.startlife - p.life) / p.startlife;
p.color.interpolate(startColor, endColor, b);
p.size = FastMath.interpolateLinear(b, startSize, endSize);
p.angle += p.rotateSpeed * tpf;
+
+ if (!selectRandomImage) {
+ p.imageIndex = (int) (b * imagesX * imagesY);
+ }
+
+ if (particleAffector != null){
+ particleAffector.affectParticle(tpf, p);
+ }
+
+ if (enableVelocityIntegration){
+ // applying gravity
+ p.velocity.x -= gravity.x * tpf;
+ p.velocity.y -= gravity.y * tpf;
+ p.velocity.z -= gravity.z * tpf;
+ temp.set(p.velocity).multLocal(tpf);
+ p.position.addLocal(temp);
+ }
+
// Computing bounding volume
temp.set(p.position).addLocal(p.size, p.size, p.size);
@@ -948,10 +1014,6 @@ public class ParticleEmitter extends Geometry {
temp.set(p.position).subtractLocal(p.size, p.size, p.size);
min.minLocal(temp);
- if (!selectRandomImage) {
- p.imageIndex = (int) (b * imagesX * imagesY);
- }
-
if (firstUnUsed < i) {
this.swap(firstUnUsed, i);
if (i == lastUsed) {
@@ -1010,6 +1072,9 @@ public class ParticleEmitter extends Geometry {
*/
public void updateFromControl(float tpf) {
if (enabled) {
+ if (particleAffector != null){
+ particleAffector.updateForFrame(tpf);
+ }
this.updateParticleState(tpf);
}
}
@@ -1150,4 +1215,12 @@ public class ParticleEmitter extends Geometry {
}
}
}
+
+ public ParticleAffector getParticleAffector() {
+ return particleAffector;
+ }
+
+ public void setParticleAffector(ParticleAffector particleAffector) {
+ this.particleAffector = particleAffector;
+ }
}
diff --git a/src/core/com/jme3/effect/ParticleTriMesh.java b/src/core/com/jme3/effect/ParticleTriMesh.java
index 55bd936..646ac25 100644
--- a/src/core/com/jme3/effect/ParticleTriMesh.java
+++ b/src/core/com/jme3/effect/ParticleTriMesh.java
@@ -186,7 +186,14 @@ public class ParticleTriMesh extends ParticleMesh {
faceNormal.cross(up, left);
up.multLocal(p.size);
left.multLocal(p.size);
- }else if (p.angle != 0){
+ }
+ else if (!p.normal.equals(Vector3f.ZERO)){
+ up.set(p.normal).crossLocal(Vector3f.UNIT_X);
+ p.normal.cross(up, left);
+ up.multLocal(p.size);
+ left.multLocal(p.size);
+ }
+ else if (p.angle != 0){
float cos = FastMath.cos(p.angle) * p.size;
float sin = FastMath.sin(p.angle) * p.size;
diff --git a/src/core/com/jme3/effect/influencers/NewtonianParticleInfluencer.java b/src/core/com/jme3/effect/influencers/NewtonianParticleInfluencer.java
index e5321dd..dc1f4d1 100644
--- a/src/core/com/jme3/effect/influencers/NewtonianParticleInfluencer.java
+++ b/src/core/com/jme3/effect/influencers/NewtonianParticleInfluencer.java
@@ -10,6 +10,7 @@ import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.math.FastMath;
import com.jme3.math.Matrix3f;
+import com.jme3.math.Vector3f;
/**
* This influencer calculates initial velocity with the use of the emitter's shape.
@@ -23,20 +24,44 @@ public class NewtonianParticleInfluencer extends DefaultParticleInfluencer {
protected float surfaceTangentFactor;
/** Emitters tangent rotation factor. */
protected float surfaceTangentRotation;
+
+ /**
+ * How far along the normal do we start the particle?
+ * */
+ protected float normalOffset;
+ protected boolean faceSurfaceNormal = false;
+
/**
* Constructor. Sets velocity variation to 0.0f.
*/
public NewtonianParticleInfluencer() {
this.velocityVariation = 0.0f;
}
+
+ public void setFaceNormal(boolean face){
+ faceSurfaceNormal = face;
+ }
+
+ public void setNormalOffset(float offset){
+ normalOffset = offset;
+ }
@Override
public void influenceParticle(Particle particle, EmitterShape emitterShape) {
emitterShape.getRandomPointAndNormal(particle.position, particle.velocity);
+ if (faceSurfaceNormal){
+ particle.normal.set(particle.velocity);
+ }
+
+ if (normalOffset != 0f){
+ temp.set(particle.velocity).multLocal(normalOffset);
+ particle.position.addLocal(temp);
+ }
+
// influencing the particle's velocity
if (surfaceTangentFactor == 0.0f) {
- particle.velocity.multLocal(normalVelocity);
+ applyNormalVelocity(particle.velocity);
} else {
// calculating surface tangent (velocity contains the 'normal' value)
temp.set(particle.velocity.z * surfaceTangentFactor, particle.velocity.y * surfaceTangentFactor, -particle.velocity.x * surfaceTangentFactor);
@@ -46,15 +71,20 @@ public class NewtonianParticleInfluencer extends DefaultParticleInfluencer {
temp = m.multLocal(temp);
}
// applying normal factor (this must be done first)
- particle.velocity.multLocal(normalVelocity);
+ applyNormalVelocity(particle.velocity);
// adding tangent vector
particle.velocity.addLocal(temp);
}
if (velocityVariation != 0.0f) {
this.applyVelocityVariation(particle);
}
+
}
+ protected void applyNormalVelocity(Vector3f velocity){
+ velocity.multLocal(normalVelocity);
+ }
+
/**
* This method returns the normal velocity factor.
* @return the normal velocity factor
diff --git a/src/core/com/jme3/effect/shapes/EmitterBoxShape.java b/src/core/com/jme3/effect/shapes/EmitterBoxShape.java
index cf6e89d..52eb640 100644
--- a/src/core/com/jme3/effect/shapes/EmitterBoxShape.java
+++ b/src/core/com/jme3/effect/shapes/EmitterBoxShape.java
@@ -115,4 +115,9 @@ public class EmitterBoxShape implements EmitterShape {
min = (Vector3f) ic.readSavable("min", null);
len = (Vector3f) ic.readSavable("length", null);
}
+
+ @Override
+ public boolean isReady(){
+ return true;
+ }
}
diff --git a/src/core/com/jme3/effect/shapes/EmitterMeshVertexShape.java b/src/core/com/jme3/effect/shapes/EmitterMeshVertexShape.java
index 07a54b6..9edc5ed 100644
--- a/src/core/com/jme3/effect/shapes/EmitterMeshVertexShape.java
+++ b/src/core/com/jme3/effect/shapes/EmitterMeshVertexShape.java
@@ -156,4 +156,9 @@ public class EmitterMeshVertexShape implements EmitterShape {
this.normals = tmpNormals;
}
}
+
+ @Override
+ public boolean isReady(){
+ return true;
+ }
}
diff --git a/src/core/com/jme3/effect/shapes/EmitterPointShape.java b/src/core/com/jme3/effect/shapes/EmitterPointShape.java
index f8ba70e..5f91f51 100644
--- a/src/core/com/jme3/effect/shapes/EmitterPointShape.java
+++ b/src/core/com/jme3/effect/shapes/EmitterPointShape.java
@@ -93,4 +93,9 @@ public class EmitterPointShape implements EmitterShape {
public void read(JmeImporter im) throws IOException {
this.point = (Vector3f) im.getCapsule(this).readSavable("point", null);
}
+
+ @Override
+ public boolean isReady(){
+ return true;
+ }
}
diff --git a/src/core/com/jme3/effect/shapes/EmitterShape.java b/src/core/com/jme3/effect/shapes/EmitterShape.java
index c23c19d..a3d10c9 100644
--- a/src/core/com/jme3/effect/shapes/EmitterShape.java
+++ b/src/core/com/jme3/effect/shapes/EmitterShape.java
@@ -61,4 +61,6 @@ public interface EmitterShape extends Savable, Cloneable {
* @return deep clone of the current instance of the emitter shape
*/
public EmitterShape deepClone();
+
+ public boolean isReady();
}
diff --git a/src/core/com/jme3/effect/shapes/EmitterSphereShape.java b/src/core/com/jme3/effect/shapes/EmitterSphereShape.java
index 1ba5aa8..ccc7ffd 100644
--- a/src/core/com/jme3/effect/shapes/EmitterSphereShape.java
+++ b/src/core/com/jme3/effect/shapes/EmitterSphereShape.java
@@ -114,4 +114,9 @@ public class EmitterSphereShape implements EmitterShape {
center = (Vector3f) ic.readSavable("center", null);
radius = ic.readFloat("radius", 0);
}
+
+ @Override
+ public boolean isReady(){
+ return true;
+ }
}[/patch]
Uh, so, that patch is actually against whenever I forked off from y’all. July 15, 2011 1600h, from the subversion repo.
I’d continuously integrate… except I can’t seem to figure out how to get git and svn to work together nicely. So, that’s what I can give you for right now.