Behind the Scenes: Flamethrower | jMonkeyEngine VFX

Here is a quick sample of what I would do. This check collision and shortens the life of the particle, so that it still (Visually) hits the player, since the hit box would make the particle disappear before hitting him.

This is just a simple quick example of something that is easy. You could do this without coding under Particle Monkey, using JME Particle system would require coding, since you can’t add more than one influencer, I use my own JME conversion that allows multiple influencers.

4 Likes

The result is interesting, I’m doing some more tests, but I would like to evaluate your idea before trying other solutions.

  • Could you please share the code of the BasicPhysicsInfluencer class you use in the video?
  • How did you calculate the origin and direction of the collision ray?
  • How do you turn off the particles?

I hypothesized something like this:

public class BasicPhysicsInfluencer implements ParticleInfluencer {

	private Node rootNode;
	private Ray tempRay = new Ray();
	private CollisionResults tempResults = new CollisionResults();
	// other variables?
	...

	public BasicPhysicsInfluencer(Node rootNode) {
		this.rootNode = rootNode;
	}

	@Override
	public void influenceParticle(Particle particle, EmitterShape emitterShape) {
	
		// how did you calculate the origin and direction of the ray?
		tempRay.origin.set(particle.position);
		tempRay.direction.set(particle.velocity);
		float length = tempRay.direction.length();
		tempRay.direction.normalizeLocal();
		//tempRay.limit = length * tpf; ???
		tempResults.clear();
		
		rootNode.collideWith(tempRay, tempResults);
		if (tempResults.size() > 0) {
			particle.life = 0; // it's correct? ???
		}
	}
	
	// getters & setters
	...
	
}

Here is something that will draw a line for the ray. I added a variable “Line” to the particle variable. So I don’t use Particle, I changed mine to ParticleEnhanced, that has a ‘public Line line;’ public variable. So I can draw a line showing the ray check.

public void update(ParticleEnhanced p, float tpf, Vector3f gravity, boolean endOfLifeGround)
{

//		final boolean debug = false;
//		
//		if (debug)
//		{
//	        if (p.line == null)
//	        {
//	    		Line locLine = new Line(p.position, tempRay.direction);
//	    		
//	    		Geometry rayLine = new Geometry("ray",locLine);
//	    		Material lineMat = new Material(GuiGlobals.assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
//	    		lineMat.setColor("Color", ColorRGBA.Red);
//	    		rayLine.setMaterial(lineMat);
//	    		
//	    		p.line = locLine;
//	    		p.geom = rayLine;
//	    		rootNode.attachChild(rayLine);
//	        } else {
//	        	p.line.updatePoints(p.position, tempRay.direction);
//	        }
//		}

Here is a quick sample for a physic check but Particle Monkey has one where this came from. But this class will not work with JME Particle System. This is from mine and it a hybrid of JME and Particle Monkey.

package test;

import java.io.IOException;

import com.jme3.collision.CollisionResult;
import com.jme3.collision.CollisionResults;
import com.jme3.effect.Particle;
import com.jme3.effect.influencers.ParticleInfluencer;
import com.jme3.effect.shapes.EmitterShape;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Ray;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Line;
import com.jme3.util.clone.Cloner;
import com.landbeyond.alternatereality.weather.particle.ParticleEnhanced;
import com.landbeyond.engine.GuiGlobals;
import com.landbeyond.engine.particle.LBBasicParticleInfluencer;

public class BasicPhysicsFlameInfluencer extends LBBasicParticleInfluencer
{
	/** Temporary variable used to help with calculations. */
	protected transient Vector3f temp = new Vector3f();
	private Ray tempRay = new Ray();
	private CollisionResults tempResults = new CollisionResults();
	private Spatial collisionMesh;

    
	public BasicPhysicsFlameInfluencer(Spatial geo)
	{
		collisionMesh = geo;
	}

	@Override
	public void influenceParticle(Particle particle, EmitterShape emitterShape)
	{
//		emitterShape.getRandomPoint(particle.position);
		this.applyVelocityVariation((ParticleEnhanced) particle, emitterShape);
	}

	/**
	 * This method applies the variation to the particle with already set velocity.
	 * 
	 * @param particle the particle to be affected
	 */
	protected void applyVelocityVariation(ParticleEnhanced p, EmitterShape emitter)
	{
	}

	
	public void update(ParticleEnhanced p, float tpf, Vector3f gravity, boolean endOfLifeGround)
	{
        
		tempRay.origin.set(p.position);
		tempRay.direction.set(p.velocity);
		float length = tempRay.direction.length();
	    tempRay.direction.normalizeLocal();
		tempRay.limit = length * tpf;
		tempResults.clear();

		temp.set(tempRay.direction);
		p.position.mult(tempRay.direction, temp);
		tempRay.direction.set(p.position.add(p.velocity.mult(0.8f)));

		
//		final boolean debug = false;
//		
//		if (debug)
//		{
//	        if (p.line == null)
//	        {
//	    		Line locLine = new Line(p.position, tempRay.direction);
//	    		
//	    		Geometry rayLine = new Geometry("ray",locLine);
//	    		Material lineMat = new Material(GuiGlobals.assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
//	    		lineMat.setColor("Color", ColorRGBA.Red);
//	    		rayLine.setMaterial(lineMat);
//	    		
//	    		p.line = locLine;
//	    		p.geom = rayLine;
//	    		GuiGlobals.rootNode.attachChild(rayLine);
//	        } else {
//	        	p.line.updatePoints(p.position, tempRay.direction);
//	        }
//		}

		

        collisionMesh.collideWith(tempRay, tempResults);
		if (tempResults.size() > 0)
		{
			for (int i = 0; i < tempResults.size(); i++)
			{
                CollisionResult closest  = tempResults.getClosestCollision();
//                if (closest.getDistance() < 1.0f)
				{
					System.out.println("end of life");
					p.life = 0.07f;
					return;
				}
			}
		}
	}
	
	@Override
	public void write(JmeExporter ex) throws IOException
	{
		// TODO Auto-generated method stub

	}

	@Override
	public void read(JmeImporter im) throws IOException
	{
		// TODO Auto-generated method stub

	}

	@Override
	public Object jmeClone()
	{
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void cloneFields(Cloner cloner, Object original)
	{
		// TODO Auto-generated method stub

	}

	@Override
	public ParticleInfluencer clone()
	{
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void setInitialVelocity(Vector3f initialVelocity)
	{
	}

	@Override
	public Vector3f getInitialVelocity()
	{
		return null;
	}

	@Override
	public void setVelocityVariation(float variation)
	{
	}

	@Override
	public float getVelocityVariation()
	{
		return 0;
	}


}

1 Like

Hi guys,
I have been reflecting on your suggestions and thinking of an alternative solution based on the engine’s stock particle system.

Let me know what you think (see the video to the end).

The yellow cube could be an enemy, in fact it is not configured to deflect flames. In this case I hypothesized that the surfaces of a human body are engulfed in flames and do not reflect them, so the bodies that are behind the first person are also hit by the flames. The two rectangular walls instead represent obstacles that deflect the flames, preventing it from passing further.

I applied this theory to the prototype of the flamethrower.
It is now possible to hit 3 enemies in rows at the same time.
I added the ‘TranslucentBucketFilter’ filter for Soft Particles.

You can run the tests yourself by downloading the sources from here. The ParticleVelocityInfluencerControl class detects collisions with physical objects and modifies the initial velocity and velocity variation of the particles.

Here is the documentation on the value that the initial velocity and velocity variation can assume (in addition to all the other parameters):

The equations are these:

List<PhysicsRayTestResult> results = physicsSpace.rayTest(beginVec, finalVec);

Vector3f velocity = new Vector3f(initialVelocity);
float variation = velocityVariation;

for (PhysicsRayTestResult ray : results) {
   
   PhysicsCollisionObject pco = ray.getCollisionObject();
   Spatial target = (Spatial) pco.getUserObject();

   if (!ignoreTag.equals(target.getUserData("TagName"))) {
       // Vector3f hitPoint = new Vector3f().interpolateLocal(beginVec, finalVec, ray.getHitFraction());
       float distance = finalVec.subtract(beginVec).length() * ray.getHitFraction();
       variation = (1f / distance) * 0.5f; // <-- here
       velocity.set(axisDir).multLocal(distance); // <-- here
       break;
   }
}

updateParticle(velocity, variation);

If the obstacle is within the collision radius, the dispersion (variation) of the particles increases and the initial velocity decreases. Could you suggest me some better equations or do you think these are okay?

Thanks as always for your help.

3 Likes

There would be couple things I would change. You want this to be a generic add-on feature for JME, right?

1st. Would make an physics based influencer, not everyone is using physics.
2nd Make sure it works with Minie Physics, JBullet is not really supported anymore.
3rd, You are “NEW” a velocity variable for every emitter every frame. That is tons of garbage collection. I would change it to be part of the class so you don’t allocate memory every every for every frame for every particle.

Honestly, The JME Particle system needs help. Having to create a AbstractControl to do this is wrong. But JME Particle system is OLD and unmaintained. I’ve asked about Particle Monkey being part of it but that was shot down because of no editor for the IDE, I know a few people have done one but for personal usage.

JME Particle could be changed quickly to allow it to be expanded and change it to make it support multiple influencers and change the update system.
There is nothing wrong with performance for the JME Particle and functions, if it could be extended when someone needs it to be. But, that idea didn’t go over well when I talked about it,

It appears, they want Particle Monkey, but unwilling to make that change until someone does a editor for it, and that has been years. Since JME is really a “HANDS ON” engine, not a “POINT CLICK”, I personally don’t see that should stop someone.

Thanks for your reply.

The key point of my idea is to use the engine’s stock particle system, without modifying its core functions. I want to give it a try before starting to study all the code of a new library, which I do not know if it is still maintained and without an editor that I should also write myself. Don’t get me wrong, it’s a great study project, but for the moment I prefer to stay within the community perimeter to be able to report bugs and help improve the engine.

  1. The idea I have described can be used in cases similar to mine, but it is not intended as an addon or as a PR for the engine.
  1. I think Minie has been the new standard physics engine for several years now. I have removed jbullet from all my projects.
  1. Thanks for the suggestion, I have optimized other things in the gist.

In my algorithm I don’t want to check the collision of every single particle. In my opinion it is not necessary in this case. The flow of particles moves from a point of origin towards a specific direction. IMO, to optimize and reduce the calculations, it is sufficient to perform a single collision test for the whole Emitter and adjust the initialVelocity and the velocityVariation of all the particles in one go, according to the result obtained. For this reason an AbstractControl is more suitable and more flexible in this case than a ParticleInfluencer.

The ParticleInfluencer.influenceParticle(Particle p, EmitterShape s) method is called for each individual particle. If my Emitter were configured with 500 particles per second at 60 fps I would go through 30.000 collision tests per second. The idea I propose for the prototype flamethrower is to perform only 60 collision tests per second which could be further reduced by adding a frequency. The result is not very precise but certainly optimized for the purpose.


  • Do you have any other ideas applicable to the engine’s stock particle system?
  • Do you have better equations to suggest for the adjustment of the initialVelocity and of the velocityVariation?

variation = (1f / distance) * 0.5f;
velocity.set(axisDir).multLocal(distance);


Thanks

1 Like