2

I have this working code of an elastic collision in canvas. How can I edit this code to change the bubbles with letters? For example I would like four letters 'A', 'B', 'C', 'D' bouncing each other. Do you have better solution than canvas? thank you

Here the working codepen https://codepen.io/andreamante/pen/MxxxEB

var Ball = function(hue, r, o, v) {
  var k = EXPLAIN_MODE?4:1, 
      l = 100;//luminosita

  this.hue = hue || rand(360, 0, 1);
  this.c = 'hsl('+ this.hue +',100%,' + l + '%)';

  this.r = r || 50;

  this.o = o || null;

  this.init = function() {
    if(!this.o) {
      this.o = {
        'x': rand(w - this.r, this.r, 1), 
        'y': rand(h - this.r, this.r, 1)
      };
    }

    if(!this.v) {
      this.v = {
        'x': randSign()*rand(sqrt(k)*4, k), 
        'y': randSign()*rand(sqrt(k)*4, k)
      };
    }
  };
  • The problem lies with computing where, when and at what velocity - both linear and rotational, that objects collide. Just the physics computations are a lot of work. Fortunately, the excellent physics library, Box2D (as used by the game Angry Birds) is available for use. You can define your letters as polygonal objects and Box2D will work out the physics for you. – enhzflep Mar 25 '19 at 18:30

1 Answers1

0

One possible solution: initialize each "Ball" with a letter, and then use the ctxt.strokeText to draw the letter instead of a circle. Here's a modification of your codepen, with comments on the modified lines:

Object.getOwnPropertyNames(Math).map(function(p) {
  window[p] = Math[p];
});

if (!hypot) {
  var hypot = function(x, y) {
    return sqrt(pow(x, 2) + pow(y, 2));
  }
}

var rand = function(max, min, is_int) {
  var max = ((max - 1) || 0) + 1,
    min = min || 0,
    gen = min + (max - min) * random();

  return (is_int) ? round(gen) : gen;
};

var randSign = function(k) {
  return (random() < (k || .5)) ? -1 : 1;
};

var sigma = function(n) {
  return n / abs(n);
};

var mu = function(values, weights) {
  var n = min(values.length, weights.length),
    num = 0,
    den = 0;

  for (var i = 0; i < n; i++) {
    num += weights[i] * values[i];
    den += weights[i];
  }

  return num / den;
}

var N_BALLS = 6,
  EXPLAIN_MODE = false,
  balls = [],
  // declare the set of letters to use
  letters = ["A", "B", "C", "D", "E", "F"],
  c = document.querySelector('canvas'),
  w, h,
  ctx = c.getContext('2d'),
  r_id = null,
  running = true;

var Segment = function(p1, p2) {
  this.p1 = p1 || null;
  this.p2 = p2 || null;
  this.alpha = null;

  this.init = function() {
    if (!this.p1) {
      this.p1 = {
        'x': rand(w, 0, 1),
        'y': rand(h, 0, 1)
      };
    }

    if (!this.p2) {
      this.p2 = {
        'x': rand(w, 0, 1),
        'y': rand(h, 0, 1)
      };
    }

    this.alpha = atan2(this.p2.y - this.p1.y,
      this.p2.x - this.p1.x);
  };

  this.init();
};

// initialize Ball() with a letter
var Ball = function(hue, letter, r, o, v) {
  var k = EXPLAIN_MODE ? 4 : 1,
    l = 100; //luminosita

  this.hue = hue || rand(360, 0, 1);
  this.c = 'hsl(' + this.hue + ',100%,' + l + '%)';

  this.r = r || 50;

  this.o = o || null;

  // assign the letter argument to a local variable in Ball()
  this.letter = letter;

  this.init = function() {
    if (!this.o) {
      this.o = {
        'x': rand(w - this.r, this.r, 1),
        'y': rand(h - this.r, this.r, 1)
      };
    }

    if (!this.v) {
      this.v = {
        'x': randSign() * rand(sqrt(k) * 4, k),
        'y': randSign() * rand(sqrt(k) * 4, k)
      };
    }
  };

  this.handleWallHits = function(dir, lim, f) {
    var cond = (f === 'up') ?
      (this.o[dir] > lim) :
      (this.o[dir] < lim);

    if (cond) {
      this.o[dir] = lim;
      this.v[dir] *= -1;
    }
  };

  this.keepInBounds = function() {
    this.handleWallHits('x', this.r, 'low');
    this.handleWallHits('x', w - this.r, 'up');
    this.handleWallHits('y', this.r, 'low');
    this.handleWallHits('y', h - this.r, 'up');
  };

  this.move = function() {
    this.o.x += this.v.x;
    this.o.y += this.v.y;

    this.keepInBounds();
  };

  this.distanceTo = function(p) {
    return hypot(this.o.x - p.x, this.o.y - p.y);
  };

  this.collidesWith = function(b) {
    return this.distanceTo(b.o) < (this.r + b.r);
  };

  this.handleBallHit = function(b, ctxt) {
    var theta1, theta2,

      /* the normal segment */
      ns = new Segment(this.o, b.o),

      /* contact point */
      cp = {
        'x': mu([this.o.x, b.o.x], [b.r, this.r]),
        'y': mu([this.o.y, b.o.y], [b.r, this.r])
      };

    this.cs = {
      'x': sigma(cp.x - this.o.x),
      'y': sigma(cp.y - this.o.y)
    };
    b.cs = {
      'x': sigma(cp.x - b.o.x),
      'y': sigma(cp.y - b.o.y)
    };

    this.o = {
      'x': cp.x -
        this.cs.x * this.r * abs(cos(ns.alpha)),
      'y': cp.y -
        this.cs.y * this.r * abs(sin(ns.alpha))
    };
    b.o = {
      'x': cp.x - b.cs.x * b.r * abs(cos(ns.alpha)),
      'y': cp.y - b.cs.y * b.r * abs(sin(ns.alpha))
    };

    if (EXPLAIN_MODE) {
      ctxt.clearRect(0, 0, w, h);
      this.draw(ctxt);
      b.draw(ctxt);

      this.connect(b, ctxt);
    }

    this.v.alpha = atan2(this.v.y, this.v.x);
    b.v.alpha = atan2(b.v.y, b.v.x);

    this.v.val = hypot(this.v.y, this.v.x);
    b.v.val = hypot(b.v.y, b.v.x);

    theta1 = ns.alpha - this.v.alpha;
    theta2 = ns.alpha - b.v.alpha;

    this.v.alpha -= PI - 2 * theta1;
    b.v.alpha -= PI - 2 * theta2;

    this.v.x = this.v.val * cos(this.v.alpha);
    this.v.y = this.v.val * sin(this.v.alpha);

    b.v.x = b.v.val * cos(b.v.alpha);
    b.v.y = b.v.val * sin(b.v.alpha);

    if (EXPLAIN_MODE) {
      ctxt.setLineDash([0]);
      this.drawV(ctxt, 'gold');
      b.drawV(ctxt, 'blue');

      running = false;
      cancelAnimationFrame(r_id);
    }
  };

  this.connect = function(b, ctxt) {
    ctxt.strokeStyle = '#fff';
    ctxt.setLineDash([5]);

    ctxt.beginPath();
    ctxt.moveTo(this.o.x, this.o.y);
    ctxt.lineTo(b.o.x, b.o.y);
    ctxt.closePath();
    ctxt.stroke();
  };

  this.drawV = function(ctxt, lc) {
    var m = 32;

    ctxt.strokeStyle = lc || this.c;

    ctxt.beginPath();
    ctxt.moveTo(this.o.x, this.o.y);
    ctxt.lineTo(this.o.x + m * this.v.x,
      this.o.y + m * this.v.y);
    ctxt.closePath();
    ctxt.stroke();
  };

  this.draw = function(ctxt) {
    ctxt.strokeStyle = this.c;
    // draw the letter instead of a circle
    ctxt.font = "80px Georgia";
    ctxt.strokeText(this.letter, this.o.x, this.o.y);

    if (EXPLAIN_MODE) {
      this.drawV(ctxt);
    }
  };

  this.init();
};

var init = function() {
  var s = getComputedStyle(c),
    hue;

  w = c.width = ~~s.width.split('px')[0];
  h = c.height = ~~s.height.split('px')[0];

  if (r_id) {
    cancelAnimationFrame(r_id);
    r_id = null;
  }

  balls = [];

  ctx.lineWidth = 3;

  if (EXPLAIN_MODE) {
    N_BALLS = 2;
    running = true;
  }

  for (var i = 0; i < N_BALLS; i++) {
    hue = EXPLAIN_MODE ? (i * 169 + 1) : null;
    balls.push(new Ball(hue, letters[i]));
  }

  handleCollisions();

  draw();
};

var handleCollisions = function() {
  var collis = false;

  do {
    for (var i = 0; i < N_BALLS; i++) {
      for (var j = 0; j < i; j++) {
        if (balls[i].collidesWith(balls[j])) {
          balls[i].handleBallHit(balls[j], ctx);
        }
      }
    }
  } while (collis);
};

var draw = function() {
  ctx.clearRect(0, 0, w, h);

  for (var i = 0; i < N_BALLS; i++) {
    ctx.setLineDash([0]);
    balls[i].draw(ctx);
    balls[i].move();
    handleCollisions();
  }

  if (!EXPLAIN_MODE || running) {
    r_id = requestAnimationFrame(draw);
  }
};

setTimeout(function() {
  init();

  addEventListener('resize', init, false);
  c.addEventListener('dblclick', init, false);
  addEventListener('keydown', function(e) {
    if (e.keyCode == 13) {
      //EXPLAIN_MODE = !EXPLAIN_MODE;
      //init();
    }
  }, false);
}, 15);
html,
body,
canvas {
  height: 100%
}

html {
  overflow: hidden;
}

body {
  margin: 0
}

canvas {
  width: 100%;
  background: #000;
}
<canvas></canvas>
rphv
  • 5,409
  • 3
  • 29
  • 47
  • Is it also possible to add random rotation of letters? – Andrea Mantegazza Mar 28 '19 at 17:34
  • Yes, but it takes a little finesse to rotate only the shape/letter. Take a look at this answer: https://stackoverflow.com/a/11985464/1612562 It should be possible to modify that function to achieve what you want. – rphv Mar 28 '19 at 19:24