Skip to content

Instantly share code, notes, and snippets.

@capdevon
Last active June 27, 2022 11:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save capdevon/0d5c929efe04fb1d750ed8083121e513 to your computer and use it in GitHub Desktop.
Save capdevon/0d5c929efe04fb1d750ed8083121e513 to your computer and use it in GitHub Desktop.
Behind the Scenes: Flamethrower | jMonkeyEngine VFX
package mygame;
import java.util.List;
import com.jme3.app.Application;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.PhysicsSpace;
import com.jme3.bullet.collision.PhysicsCollisionObject;
import com.jme3.bullet.collision.PhysicsRayTestResult;
import com.jme3.bullet.debug.DebugTools;
import com.jme3.effect.ParticleEmitter;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.AbstractControl;
/**
* https://wiki.jmonkeyengine.org/docs/3.4/core/effect/particle_emitters.html
*
* @author capdevon
*/
public class ParticleVelocityInfluencerControl extends AbstractControl {
private PhysicsSpace physicsSpace;
private DebugTools debugTools;
private ColorRGBA debugColor = ColorRGBA.Magenta.clone();
private boolean debugEnabled;
private Vector3f initialVelocity = new Vector3f(5, 0, 0);
private float velocityVariation = 0.1f;
private Vector3f axisDir = new Vector3f(1, 0, 0);
private float maxDistance = 5f;
// Objects with this tag will be ignored.
private String ignoreTag = "";
/**
* Constructor.
*
* @param app
*/
public ParticleVelocityInfluencerControl(Application app) {
debugTools = new DebugTools(app.getAssetManager());
physicsSpace = app.getStateManager().getState(BulletAppState.class).getPhysicsSpace();
}
@Override
protected void controlUpdate(float tpf) {
Vector3f origin = spatial.getWorldTranslation();
Vector3f direction = spatial.getWorldRotation().mult(axisDir);
checkCollision(origin, direction);
}
private void checkCollision(Vector3f origin, Vector3f direction) {
TempVars t = TempVars.get();
Vector3f beginVec = t.vect1.set(origin);
Vector3f finalVec = t.vect2.set(direction).multLocal(maxDistance).addLocal(origin);
if (debugEnabled) {
debugTools.setMagentaArrow(beginVec, direction);
}
List<PhysicsRayTestResult> results = physicsSpace.rayTest(beginVec, finalVec);
Vector3f velocity = t.vect3.set(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;
velocity.set(axisDir).multLocal(distance);
break;
}
}
updateParticle(velocity, variation);
t.release();
}
public void updateParticle(Vector3f velocity, float variation) {
ParticleEmitter emitter = (ParticleEmitter) spatial;
emitter.getParticleInfluencer().setVelocityVariation(variation);
emitter.getParticleInfluencer().setInitialVelocity(velocity);
}
@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
if (debugEnabled) {
debugTools.show(rm, vp);
}
}
public ColorRGBA getDebugColor() {
return debugColor;
}
public void setDebugColor(ColorRGBA debugColor) {
this.debugColor.set(debugColor);
debugTools.DEBUG_MAGENTA.setColor("Color", debugColor);
}
public boolean isDebugEnabled() {
return debugEnabled;
}
public void setDebugEnabled(boolean debugEnabled) {
this.debugEnabled = debugEnabled;
}
public Vector3f getAxisDir() {
return axisDir;
}
public void setAxisDir(Vector3f axisDir) {
this.axisDir.set(axisDir);
}
public float getMaxDistance() {
return maxDistance;
}
public void setMaxDistance(float maxDistance) {
this.maxDistance = maxDistance;
}
public Vector3f getInitialVelocity() {
return initialVelocity;
}
public void setInitialVelocity(Vector3f initialVelocity) {
this.initialVelocity.set(initialVelocity);
}
public float getVelocityVariation() {
return velocityVariation;
}
public void setVelocityVariation(float velocityVariation) {
this.velocityVariation = velocityVariation;
}
public String getIgnoreTag() {
return ignoreTag;
}
public void setIgnoreTag(String ignoreTag) {
this.ignoreTag = ignoreTag;
}
}
package mygame;
import com.jme3.app.SimpleApplication;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.collision.shapes.CollisionShape;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.bullet.util.CollisionShapeFactory;
import com.jme3.effect.ParticleEmitter;
import com.jme3.effect.ParticleMesh.Type;
import com.jme3.input.KeyInput;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.post.FilterPostProcessor;
import com.jme3.post.filters.TranslucentBucketFilter;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.debug.Grid;
import com.jme3.scene.debug.WireBox;
import com.jme3.scene.debug.WireSphere;
import com.jme3.scene.shape.Box;
import com.jme3.scene.shape.Line;
import com.jme3.system.AppSettings;
import com.jme3.util.SkyFactory;
/**
* https://github.com/jMonkeyEngine/jmonkeyengine/blob/master/jme3-examples/src/main/java/jme3test/effect/TestExplosionEffect.java
*
* @author capdevon
*/
public class Test_SoftParticles extends SimpleApplication implements ActionListener {
/**
* Start the jMonkeyEngine application
*
* @param args
*/
public static void main(String[] args) {
Test_SoftParticles app = new Test_SoftParticles();
AppSettings settings = new AppSettings(true);
settings.setResolution(1280, 720);
settings.setSamples(1);
app.setSettings(settings);
app.setShowSettings(false);
app.setPauseOnLostFocus(false);
app.start();
}
private boolean softParticles = true;
private boolean pointSprite = true;
private FilterPostProcessor fpp;
private TranslucentBucketFilter tbf;
private Node particleNode;
private BulletAppState physics;
@Override
public void simpleInitApp() {
cam.setLocation(new Vector3f(-7.2221026f, 4.1183004f, 7.759811f));
cam.setRotation(new Quaternion(0.06152846f, 0.91236454f, -0.1492115f, 0.37621948f));
flyCam.setMoveSpeed(10);
initPhysics();
createSky();
createGrid();
initFilters();
createScene();
createParticles();
configInputs();
}
private void initPhysics() {
physics = new BulletAppState();
stateManager.attach(physics);
physics.setDebugEnabled(true);
}
private void configInputs() {
inputManager.addMapping("togglePhysicsDebug", new KeyTrigger(KeyInput.KEY_0));
inputManager.addMapping("toggleFilter", new KeyTrigger(KeyInput.KEY_SPACE));
inputManager.addMapping("refire", new MouseButtonTrigger(MouseInput.BUTTON_RIGHT));
inputManager.addListener(this, "togglePhysicsDebug", "toggleFilter", "refire");
}
@Override
public void onAction(String name, boolean isPressed, float tpf) {
if (name.equals("refire") && isPressed) {
// emit again
// fpp.removeFilter(tbf); // <-- add back in to fix
particleNode.detachAllChildren();
createParticles();
// fpp.addFilter(tbf);
} else if (name.equals("toggleFilter") && isPressed) {
//tbf.setEnabled(!tbf.isEnabled());
softParticles = !softParticles;
if (softParticles) {
viewPort.addProcessor(fpp);
System.out.println("TranslucentBucketFilter Added");
} else {
viewPort.removeProcessor(fpp);
System.out.println("TranslucentBucketFilter Removed");
}
} else if (name.equals("togglePhysicsDebug") && isPressed) {
boolean debug = physics.isDebugEnabled();
physics.setDebugEnabled(!debug);
}
}
@Override
public void simpleUpdate(float tpf) {
// do something...
}
private void createScene() {
particleNode = new Node("particleNode");
particleNode.setLocalTranslation(-2, 0, 0);
rootNode.attachChild(particleNode);
Geometry geom = createCube("Enemy", 0.5f, ColorRGBA.Orange);
geom.setUserData("TagName", "Enemy");
createMeshCollider(geom, 0f);
rootNode.attachChild(geom);
Geometry geom2 = makeShape("Obstacle.1", new Box(0.05f, 1, 1), ColorRGBA.Cyan);
geom2.getMaterial().setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg"));
geom2.setLocalTranslation(-1, 0, 0);
createMeshCollider(geom2, 0f);
rootNode.attachChild(geom2);
Geometry geom3 = makeShape("Obstacle.2", new Box(0.05f, 1, 1), ColorRGBA.Cyan);
geom3.getMaterial().setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg"));
geom3.setLocalTranslation(1, 0, 0);
createMeshCollider(geom3, 0f);
rootNode.attachChild(geom3);
}
private void initFilters() {
fpp = new FilterPostProcessor(assetManager);
tbf = new TranslucentBucketFilter(true);
fpp.addFilter(tbf);
int samples = context.getSettings().getSamples();
if (samples > 0) {
fpp.setNumSamples(samples);
}
viewPort.addProcessor(fpp);
}
private RigidBodyControl createMeshCollider(Spatial sp, float mass) {
CollisionShape shape = CollisionShapeFactory.createMeshShape(sp);
RigidBodyControl rbc = new RigidBodyControl(shape, mass);
sp.addControl(rbc);
physics.getPhysicsSpace().add(rbc);
return rbc;
}
private void createSky() {
Spatial sky = SkyFactory.createSky(assetManager, "Scenes/Beach/FullskiesSunset0068.dds", SkyFactory.EnvMapType.CubeMap);
sky.setShadowMode(RenderQueue.ShadowMode.Off);
rootNode.attachChild(sky);
}
private void createGrid() {
Node debugNode = new Node("DebugNode");
debugNode.attachChild(createGrid(Vector3f.ZERO, 21, ColorRGBA.Gray));
Vector3f xAxis = Vector3f.UNIT_X.mult(10);
debugNode.attachChild(makeShape("X", new Line(xAxis.negate(), xAxis), ColorRGBA.Red));
Vector3f zAxis = Vector3f.UNIT_Z.mult(10);
debugNode.attachChild(makeShape("z", new Line(zAxis.negate(), zAxis), ColorRGBA.Blue));
debugNode.setShadowMode(ShadowMode.Off);
rootNode.attachChild(debugNode);
}
private Geometry createWireBox(String name, float size, ColorRGBA color) {
return makeShape(name, new WireBox(size, size, size), color);
}
private Geometry createWireSphere(String name, float radius, ColorRGBA color) {
return makeShape(name, new WireSphere(radius), color);
}
private Geometry createGrid(Vector3f pos, int size, ColorRGBA color) {
Geometry geom = makeShape("Grid", new Grid(size, size, 1f), color); //1WU
geom.center().move(pos);
return geom;
}
private Geometry makeShape(String name, Mesh shape, ColorRGBA color) {
Geometry geom = new Geometry(name, shape);
Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
mat.setColor("Color", color);
geom.setMaterial(mat);
return geom;
}
private Geometry createCube(String name, float size, ColorRGBA color) {
Box box = new Box(size, size, size);
Geometry geom = new Geometry(name, box);
Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
mat.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg"));
mat.setColor("Color", color);
geom.setMaterial(mat);
return geom;
}
private void createParticles() {
ParticleEmitter fire = createEmitter("Fire");
fire.setStartColor(ColorRGBA.Orange);
fire.setEndColor(ColorRGBA.Red);
particleNode.attachChild(fire);
ParticleEmitter smoke = createEmitter("Smoke");
smoke.setStartColor(ColorRGBA.Blue);
smoke.setEndColor(ColorRGBA.DarkGray);
particleNode.attachChild(smoke);
}
private ParticleEmitter createEmitter(String name) {
Type type = pointSprite ? Type.Point : Type.Triangle;
Material mat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md");
mat.setTexture("Texture", assetManager.loadTexture("Effects/Explosion/flame.png"));
mat.setBoolean("PointSprite", pointSprite);
mat.setFloat("Softness", 3f); //
ParticleEmitter emitter = new ParticleEmitter(name, type, 60);
emitter.setMaterial(mat);
//emitter.setShape(new EmitterSphereShape(Vector3f.ZERO, 0.1f));
emitter.setImagesX(2);
emitter.setImagesY(2); // 2x2 texture animation
emitter.setLowLife(0.2f);
emitter.setHighLife(1f);
emitter.setStartSize(0.1f);
emitter.setEndSize(0.5f);
emitter.setGravity(0, 0, 0);
emitter.setFacingVelocity(true);
emitter.setSelectRandomImage(true);
emitter.getParticleInfluencer().setVelocityVariation(0.1f);
emitter.getParticleInfluencer().setInitialVelocity(new Vector3f(5, 0, 0));
emitter.setParticlesPerSec(50);
ParticleVelocityInfluencerControl control = new ParticleVelocityInfluencerControl(this);
control.setIgnoreTag("Enemy");
control.setAxisDir(Vector3f.UNIT_X);
control.setMaxDistance(5);
control.setInitialVelocity(new Vector3f(5, 0, 0));
control.setVelocityVariation(0.1f);
control.setDebugEnabled(true);
emitter.addControl(control);
return emitter;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment