Last active
June 27, 2022 11:19
-
-
Save capdevon/0d5c929efe04fb1d750ed8083121e513 to your computer and use it in GitHub Desktop.
Behind the Scenes: Flamethrower | jMonkeyEngine VFX
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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