Turn animated mesh into smoke

Live Link

Instructions: press K to smoke


I had an idea for a while of creating an effect for Might is Right, where a mesh wold turn into a bunch of particles.

The idea is quite simple:

  • compute skinned mesh vertices in world-space
  • distribute particles across the mesh surface uniformly

What do you guys think? :slight_smile:

9 Likes

Cool effect! :+1:

1 Like

Looks great! I especially like what happens when you hold down or spam the K button :grin:

1 Like

Wow, such a simple, yet cool-looking effect :smiley:

1 Like

Thanks! Yeah, it’s just a little prototyping tool I put together, there’s actually an emitted below the terrain and particles are just snapped to the mesh surface when you press K :smiley:

@DolphinIQ
Totally agree, it was quite simple to code up also, the key problem was to get performance on-par, had to inline a lot of the simple stuff like matrix transformation.

Super cool!

Do you want to share the code? I will love to learn how is done this technique.

Hey @mcanet,

Sure, I can’t share the code right now, I’m in the process of releasing my game on steam, but in a month or two I will come back to meep and release an update with this feature also.

The basic idea is to distribute points across the surface of the mesh in a random pattern so that it’s as even as possible.

To do this - you need to first calculate total surface of a mesh, then you go over each face (triangle), take it’s surface area and decide how many points to place on that face based on it’s relative surface area.

Something like this:

totalPointsToPlace = 100;
totalArea = computeTotalArea(geometry)

for(traingle in geometry){
    area = computeTraingleArea(triangle)
    relativeArea = area/totalArea;

    pintsToPlace = relativeArea*totalPointsToPlace;

    for(i =0; i<pointsToPlace; i++){
          position = computeRandomPointOnTraingle(triangle)
          placePoint(position)
    }
}

Actual code is a bit more complex, but this is a good representation. Home that helps!

3 Likes

Thanks this pseudocode is explanatory. It is really cool concept to spread 100 points around.
How many particles are in your demo? it runs super smooth.
Is the particle at the begging non-transparent? I have the feeling are all the same size.

Thanks a lot.

In the demo it’s 8000 I think. My code is super-optimized, so even 100,000 particles work without much delay,

At the start particles are… well, they are always somewhat transparent, at the start they start with about 0.3 opacity, though that works multiplicatively, so if the sprite has transparent areas (which it does) - you end up with some transparency in those areas. I think that’s pretty clear though, so I think I didn’t get your question :sweat_smile:

About particle size, they are different sized, but the distribution is only about 33%, minumum size is 0.2 and maximum is 0.3 (in world space measurements, that’s about 20 and 30cm respectively).

here’s the particle definition if you’re interested:

{
    "position": {
        "x": 9.17416,
        "y": -1.90031,
        "z": 12.06082
    },
    "scale": {
        "x": 1,
        "y": 1,
        "z": 1
    },
    "rotation": {
        "x": 0,
        "y": 0,
        "z": 0,
        "w": 1
    },
    "parameters": [
        {
            "name": "scale",
            "itemSize": 1,
            "defaultTrackValue": {
                "itemSize": 1,
                "data": [
                    1
                ],
                "positions": [
                    0
                ]
            }
        },
        {
            "name": "color",
            "itemSize": 4,
            "defaultTrackValue": {
                "itemSize": 4,
                "data": [
                    1,
                    1,
                    1,
                    1
                ],
                "positions": [
                    0
                ]
            }
        }
    ],
    "preWarm": false,
    "readDepth": true,
    "softDepth": true,
    "blendingMode": 0,
    "layers": [
        {
            "imageURL": "data/textures/particle/smokeparticle.png",
            "particleLife": {
                "min": 1,
                "max": 1.6
            },
            "particleSize": {
                "min": 0.2,
                "max": 0.3
            },
            "particleRotation": {
                "min": 0,
                "max": 0
            },
            "particleRotationSpeed": {
                "min": 0,
                "max": 0
            },
            "emissionShape": 1,
            "emissionFrom": 1,
            "emissionRate": 0,
            "emissionImmediate": 4000,
            "parameterTracks": [
                {
                    "name": "color",
                    "track": {
                        "itemSize": 4,
                        "data": [
                            0.6431372549019608,
                            0.6039215686274509,
                            0.5372549019607843,
                            0.3,
                            0.6,
                            0.5764705882352941,
                            0.5215686274509804,
                            0.265,
                            0.7098039215686275,
                            0.6745098039215687,
                            0.615686274509804,
                            0
                        ],
                        "positions": [
                            0,
                            0.7957639171068672,
                            1
                        ]
                    }
                },
                {
                    "name": "scale",
                    "track": {
                        "itemSize": 1,
                        "data": [
                            0.9235456523622187,
                            0.9685843530893341,
                            1.0034300604871111
                        ],
                        "positions": [
                            0,
                            0.31176470588235294,
                            0.7647058823529411
                        ]
                    }
                }
            ],
            "position": {
                "x": 0,
                "y": 0.6,
                "z": 0
            },
            "scale": {
                "x": 0.9,
                "y": 0.5,
                "z": 0.65
            },
            "particleVelocityDirection": {
                "direction": {
                    "x": 0,
                    "y": -1,
                    "z": 0
                },
                "angle": 0
            },
            "particleSpeed": {
                "min": 0.1,
                "max": 1.7
            }
        }
    ]
}

Particle engine itself is already in meep, so you can use that any time you like :wink:

Yes understood particles should have always some transparency to merge nicely.
Meep engine looks super good.

One question, this calculation of triangle and relate to area. Do it work better when is a low poly model?

In terms of performance - yes. I don’t know how much though. In terms of quality of distribution - I honestly don’t know, but I guess it would not make any difference. Because this method uses surface and not volume - it will suffer if your model has dual surfaces or overlapping mesh pieces.

I thought that this method would be slow, but for my purposes and with pretty much maximum optimization levels - it has almost no impact on performance. My meshes are around 10k poly each and most of the time is spent on skinning and particle initialization and not on the distribution.

Maybe it’s not clear, but you have to do CPU-side skinning for animated meshes to use this method.

1 Like

Thanks your explanations help me a lot. I did some research on the topic:

I found that area is a method integrated already in THREE.Triangle:

var t = new THREE.Triangle(va,vb,vc);
var area = t.getArea();

Then I found detail explanation and code how to compute a random point on Triangle:

function randomPointInTriangle(vertex1, vertex2, vertex3) {
  var edgeAB = vertex2.clone().sub(vertex1)
  var edgeAC = vertex3.clone().sub(vertex1)
  var r = Math.random();
  var s = Math.random();
  if (r + s >= 1) {
    r = 1 - r
    s = 1 - s
  }
  return edgeAB.multiplyScalar(r).add(edgeAC.multiplyScalar(s)).add(vertex1)
  // random point in triangle
}

Other suggest for uniform sample better:

function randomInTriangle(v1, v2, v3) {
  var r1 = Math.random();
  var r2 = Math.sqrt(Math.random());
  var a = 1 - r2;
  var b = r2 * (1 - r1);
  var c = r1 * r2;
  return (v1.clone().multiplyScalar(a)).add(v2.clone().multiplyScalar(b)).add(v3.clone().multiplyScalar(c));
}
1 Like