2
\$\begingroup\$

Which approach should I take for creating a game/app where the player, looking above the ground "canvas", would be able to "move" dirt (i.e. soil, or generally powder) around using fingers? I.e. similar to what you would achieve if you placed some dirt on a A4 white paper, and then move it around using palms/fingers?

I first thought it would be something similar to how painting apps work, but they more or less use a texture brush, and this doesn't seem to give me the desired effects. The ultimate experience would probably be to do it using a full-fledged physics particle system, but I am not sure a midrange device (Android, iOS) is capable of rendering something like that with satisfying detail in realtime? Admittedly, I am not trying to do what's shown in the video, but at least something that wouldn't look like moving "puzzles" around.

All other materials related to these search terms are merely using particles to simulate dirt flying around vehicle tires, so I am slightly stucked and I feel a nudge in the right direction might do wonders. Any algorithm/technology hints?

\$\endgroup\$
  • \$\begingroup\$ Just some simple particles with like 3 layers of particle already looks quite good, i wrote a quick demo here : jsfiddle.net/gamealchemist/4gfx1L2e \$\endgroup\$ – GameAlchemist Nov 23 '14 at 18:19
  • \$\begingroup\$ Are you trying to achieve an effect similar to what is happening in this link? The video also shows adding sand in pinches and handfuls, but you are only asking about the effect of dragging your finger through a thin layer. Is this correct? \$\endgroup\$ – Aholio Nov 23 '14 at 19:04
  • \$\begingroup\$ @Aholio: that's correct, yes. And yes, I would already have dirt places and user would mostly have to dig to remove it (so my initial attempts were in the direction demonstrated by @GameAlchemist), but I wanted to try getting a more realistic effect. \$\endgroup\$ – Lou Nov 23 '14 at 19:29
  • \$\begingroup\$ @GameAlchemist: thanks for the effort, this should certainly be an answer instead of a comment. Well, this is what I thought when I wrote "like moving puzzles", I wasn't able to get a satisfying enough effect, but that's indeed a possibility. \$\endgroup\$ – Lou Nov 23 '14 at 19:34
3
\$\begingroup\$

So i'll post my attempt here.
Idea is to have very simple particles (=speed(with friction)+position) that are layered.
The top (=0) layer shows the dust at scale 1, then every layer below has bigger scale, and those particles will move slower and slower the deeper the layer.

The difficulty here is to fine tune :
• appearance of the dust brushes (here random/with center gradient opacity).
• behavior of each particle, depending on its layer (i reduced the speed boost on lower layer, maybe the friction should be higher in lower layers too).
• size / number of particles per layer and such.

The first lines of the code are parameters that you can change to quite greatly change the way it looks.

http://jsfiddle.net/gamealchemist/4gfx1L2e/

Layered Sand

// parameters
var layerCount = 3;
var dustSize = 50;
var dustPoints = 200;
var mouseRadius = 200;
var normalSpeed = 100;
var dustMaxOpacity = 0.85;
var baseScale = 1;
var friction = 0.1;
var partCountForLayer = function (layer, layerCount) {
    var tmp = 1 + (layerCount - layer);
    return 20 * tmp * tmp;
};


var dusts = []; // stores all dust image
var parts = []; // stores all particles

// create dust images
for (var i = 0; i < 5; i++) {
    dusts.push(createDust(dustSize, dustPoints));
}

// Boilerplate
var cv = document.getElementById('cv');
var ctx = cv.getContext('2d');

// Part class : defines a dust particle
function Part(layer) {
    this.x = cv.width * Math.random();
    this.y = cv.height * Math.random();
    this.vx = 0;
    this.vy = 0;
    this.layer = layer;
    this.scale = baseScale * (1 + 3 * layer);
    this.width = dustSize * this.scale;
    this.heigth = dustSize * this.scale;
    this.whichDust = Math.floor(dusts.length * Math.random());
    this.draw = function () {
        ctx.drawImage(dusts[this.whichDust], this.x, this.y, this.width, this.heigth);
    };
    // moves the dust with its current parameters
    this.update = function () {
        this.vx -= friction * this.vx;
        this.vy -= friction * this.vy;
        this.x += this.vx;
        this.y += this.vy;
    };
    // move the dust with a brush in (x,y)  having radius
    // the normalized (0;1) move is stored in dx, dy
    this.sweep = function (x, y, radius, dx, dy) {
        var xDiff = this.x + this.width * 0.5 - x;
        var yDiff = this.y + this.heigth * 0.5 - y;
        var dist = Math.sqrt(xDiff * xDiff + yDiff * yDiff);
        if ((dist > 0.5 * this.width) && (dist > radius)) return;
        this.vx += normalSpeed * dx / (2 + 3 * this.layer);
        this.vy += normalSpeed * dy / (2 + 3 * this.layer);
    };

}


// fill the layers
for (var l = 0; l < layerCount; l++) {
    var dustCount = partCountForLayer(l, layerCount);
    for (var pi = 0; pi < dustCount; pi++) {
        parts.push(new Part(l));
    }
}

// -------------------------------------------

function animate() {
    ctx.clearRect(0, 0, cv.width, cv.height);
    ctx.fillText('-_ Swipe the dust with the mouse _-', 20, 20);
    parts.forEach(function (p) {
        p.draw();
        p.update();
    });
}

setInterval(animate, 30);

// moves all particles after a brush was used
function moveDust() {
    console.log('md');
    var dx = mouse.x - mouse.startX;
    var dy = mouse.y - mouse.startY;
    dx /= cv.width;
    dy /= cv.height;
    parts.forEach(function (p) {
        p.sweep( mouse.startX, mouse.startY, mouseRadius, dx, dy)
    });
}


// -------------------------------------------

// creates an image with random points drawn on it.
function createDust(size, count) {
    var dustImage = document.createElement('canvas');
    dustImage.width = size;
    dustImage.height = size;
    var dctx = dustImage.getContext('2d');
    var sqRad = (0.5 * size) * (0.5 * size);
    dctx.clearRect(0, 0, size, size);
    for (var i = 0; i < count; i++) {
        var x = (Math.random() - 0.5) * size;
        var y = (Math.random() - 0.5) * size;
        var d = x * x + y * y;
        if (d > sqRad) continue;
        dctx.globalAlpha = dustMaxOpacity * (1 - Math.sqrt(d / sqRad));
        dctx.fillRect(x + 0.5 * size, y + 0.5 * size, 1, 1);
    }
    return dustImage;
}

// -------------------------------------------
//       Mouse handling
// -------------------------------------------

function setupMouse(canvas, preventDefault) {
    var rectLeft, rectTop;
    var hook = canvas.addEventListener.bind(canvas);
    hook('mousedown', updateMouseStatus.bind(null, true));
    hook('mouseup', updateMouseStatus.bind(null, false));
    hook('mouseout', updateMouseStatus.bind(null, null));
    hook('mousemove', updateCoordinates);
    window.addEventListener('scroll', updateRect);

    function updateMouseStatus(b, e) {
        mouse.down = !!b;
        updateCoordinates(e);
        if (b) {
            mouse.startX = mouse.x;
            mouse.startY = mouse.y;
        } else if (b!=null) {
            moveDust();
        }

        if (preventDefault) {
            e.stopPropagation();
            e.preventDefault();
        }
    }

    function updateCoordinates(e) {
        mouse.x = (e.clientX - rectLeft);
        mouse.y = (e.clientY - rectTop);
    }

    function updateRect() {
        var rect = canvas.getBoundingClientRect();
        rectLeft = rect.left;
        rectTop = rect.top;
    }
    updateRect();
};

var mouse = {
    x: 0,
    y: 0,
    startX: 0,
    startY: 0,
    down: false
};

setupMouse(cv, true);
\$\endgroup\$
  • \$\begingroup\$ +1 Thanks, this is similar to what I've tried, I'll play with parameters a bit more. \$\endgroup\$ – Lou Nov 24 '14 at 11:41
  • \$\begingroup\$ This is basically how I did it in the end, apart from using a real ground texture for particles. Thanks! \$\endgroup\$ – Lou Dec 3 '14 at 9:49
1
\$\begingroup\$

This really isn't an answer, but hopefully a nudge in the right direction. I think my approach would be to have the background image painted with the hole dug, and then place a dirt pile image on top. When the user swipes across the dirt pile you can erase that portion of the dirt pile image. This way you will slowly reveal what is under it. sort of like a scratch ticket. You could darken the image on one edge of the swipe and lighten the opposite edge to simulate highlights and shadows.

\$\endgroup\$
  • \$\begingroup\$ Well, the problem might be that the canvas below the dirt can be white, like a piece of paper, and then the problem is reduced to creating a convincing "erasing" algorithm. Darkening edges might improve how it looks, yes, I'll have to play with it. \$\endgroup\$ – Lou Nov 24 '14 at 11:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.