弹跳粒子意外重叠

时间:2014-12-10 16:02:08

标签: javascript d3.js

我一直在 D3 中生成一组弹跳球。我已经设法让系统按照以下规则运行:

  • Orbs应该慢慢漂移
  • 考虑到质量和数量,Orbs应该相互反弹这样做的动力
  • Orbs不应该离开SVG区域,因此应该从边缘反弹

我设法找到了一个问题然而我无法弄清楚是什么原因造成的,每一次Orbs似乎都会卡住 - 一个节点会卡在另一个节点上,跟着它而不是弹跳离开,有时最终落在另一个球体的顶部,或者在另一个球体的顶部,我认为这是不可能的。

重叠逻辑相当简单,如果2个球体的中心之间的距离小于它们需要反弹的组合半径:

// Calculate the distance between the centres of 
// two nodes, we don't have take the square root
// as we don't need it, and it's an expensive operation
var x = d.x - quad.point.x;
var y = d.y - quad.point.y;
var distance = x * x + y * y;

// Calculate the radius of each to determine how
// close they need to be to be considered touching
var radii = getSize(d) + getSize(quad.point);

// If the nodes appear to be touching then we need to move the points
if (distance < (radii * radii)) {
    // Overlapping
}

如果它们反弹,我会计算它们应该反弹的角度和速度,保留动量,这是一个记录的算法。因此,我无法弄清楚两颗球如何相互跟随?

function bounce_vectors(d1, d2) {

    // Calculate the angle at which the balls collide
    var dx = d1.x - d2.x;
    var dy = (d1.y - d2.y) * -1;
    var collisionAngle = Math.atan2(dy, dx);

    // Calculate the speed of d1/d2
    var speed1 = Math.sqrt((d1.speedX * d1.speedX) + (d1.speedY * d1.speedY));
    var speed2 = Math.sqrt((d2.speedX * d2.speedX) + (d2.speedY * d2.speedY));

    // Get angles (in radians) for each ball, given current velocities
    var direction1 = Math.atan2(d1.speedY, d1.speedX);
    var direction2 = Math.atan2(d2.speedY, d2.speedX);

    // Rotate velocity vectors so we can plug into equation for conservation of momentum
    var rotatedVelocityX1 = speed1 * Math.cos(direction1 - collisionAngle);
    var rotatedVelocityY1 = speed1 * Math.sin(direction1 - collisionAngle);
    var rotatedVelocityX2 = speed2 * Math.cos(direction2 - collisionAngle);
    var rotatedVelocityY2 = speed2 * Math.sin(direction2 - collisionAngle);        

  // Update actual velocities using conservation of momentum
    var finalVelocityX1 = ((d1.size - d2.size) * rotatedVelocityX1 + (d2.size + d2.size) * rotatedVelocityX2) / (d1.size + d2.size);
    var finalVelocityX2 = ((d1.size + d1.size) * rotatedVelocityX1 + (d2.size - d1.size) * rotatedVelocityX2) / (d1.size + d2.size);

    // Y velocities remain constant
    var finalVelocityY1 = rotatedVelocityY1;
    var finalVelocityY2 = rotatedVelocityY2;

    // Rotate angles back again so the collision angle is preserved
    d1.speedX = Math.cos(collisionAngle) * finalVelocityX1 + Math.cos(collisionAngle + Math.PI/2) * finalVelocityY1;
    d1.speedY = Math.sin(collisionAngle) * finalVelocityX1 + Math.sin(collisionAngle + Math.PI/2) * finalVelocityY1;
    d2.speedX = Math.cos(collisionAngle) * finalVelocityX2 + Math.cos(collisionAngle + Math.PI/2) * finalVelocityY2;
    d2.speedY = Math.sin(collisionAngle) * finalVelocityX2 + Math.sin(collisionAngle + Math.PI/2) * finalVelocityY2;
};

// Add an additional function to the string prototype
if (!String.prototype.format) {
    String.prototype.format = function () {
        var args = arguments;
        return this.replace(/{(\d+)}/g, function (match, number) {
            return typeof args[number] != 'undefined'
              ? args[number]
              : match
            ;
        });
    };
}

d3.Orbs = function() {
   
	var width = 500;
	var height = 500;
	var maxSpeed = 10;
    var alpha = 0.026;

	var nodes = [
		{ id: 1, name: "Software", size: 5, children: [
				{ id: 4, name: "Bug" },
				{ id: 5, name: "Feature" },
				{ id: 6, name: "Build" }] 
		},
		{ id: 2, name: "Scrum", size: 8, children: [
				{ id: 7, name: "Sprint" },
				{ id: 8, name: "Story" },
				{ id: 9, name: "Story Point" }] 
		},
		{ id: 3, name: "Resource", size: 12, children: [
				{ id: 10, name: "Location" },
				{ id: 11, name: "Team" },
				{ id: 12, name: "Members" }] 
		},
        { id: 13, name: "A", size: 12	},
        { id: 14, name: "B", size: 9 },
        { id: 15, name: "C", size: 3 }
	];

    /**
     * Initialize each of the nodes in the dataset to ensure
     * that the required positions and speeds exist in the data
     * for collision avoidance and applying gravity
     * @param {array} The collection of nodes from the raw data
     * @param {object} A bounding rectangle to restrict where the nodes can be placed
     */
    function initialize_nodes(nodes, bounds) {
        // Ensure each node has a random start location and speed
        nodes.forEach(function(d) { 
            d.x = Math.random() * bounds.right;
            d.y = Math.random() * bounds.bottom;
            d.speedX = (Math.random() - 0.5) * 2 * maxSpeed;
            d.speedY = (Math.random() - 0.5) * 2 * maxSpeed;
        });
        
        /*nodes[0].x = 100;
        nodes[0].y = 100;
        nodes[0].speedX = 10;
        nodes[0].speedY = -10;
        nodes[0].size = 10;
        nodes[0].fill = 'red';
        
        nodes[1].x = 200;
        nodes[1].y = 400;
        nodes[1].speedX = -10;
        nodes[1].speedY = 40;
        nodes[1].size = 20;
        nodes[1].fill = 'steelblue';*/
    };
  
    // Initialize the nodes now
    initialize_nodes(nodes, {left: 0, top: 0, bottom: height, right: width});
        
	

	var force = d3.layout.force()
                        .alpha(alpha)
						 .gravity(0)
						 .charge(0)
						 .nodes(nodes)
						 .size([width, height])
						 .on("tick", tick);

	var svg = d3.select("body")
				.append("svg")
				.attr("width", 500)
				.attr("height", 500);

	var node = svg.selectAll(".node")
				   .data(nodes)
				   .enter()
				   .append("circle")
				   		.attr("class", "node")
                        .style('fill', function(d) { return d.fill; })
				   		.attr("cx", function(d) { return d.x; })
						.attr("cy", function(d) { return d.y; })
						.attr("r", function(d) { return getSize(d); });
	
	function getSize(node) {
		return 5 * node.size;
	}

/**
 * Return a function that applies gravity to an individual node
 * @param {number} The alpha or kinetic energy currently in the system
 * @returns {function} The funtion that applies gravity to each individual node
 */
function gravity(alpha) {
    
    /**
     * Update the position and speed of the node based upon it's current location
     * @param {Object} The data node that is being moved
     */
    return function(d) {
	    var radius = getSize(d);
        
        // If the node is attempting to move off screen then invert the speed
        // on that axis to bring it back into scope
        if ((d.x - radius) < 0) d.speedX = Math.abs(d.speedX);
        if ((d.x + radius) > width) d.speedX = -1 * Math.abs(d.speedX);
        if ((d.y - radius) < 0) d.speedY = -1 * Math.abs(d.speedY);
        if ((d.y + radius) > height) d.speedY = Math.abs(d.speedY);
        
        // Set the current position of the node
        d.x = d.x + (d.speedX * alpha);
        d.y = d.y + (-1 * d.speedY * alpha);
        
        // Apply some extra protection - if the node has moved off the screen
        // then get it back again rather than waiting for it's speed
        d.x = Math.min(Math.max(d.x, 0 + radius), width - radius);
        d.y = Math.min(Math.max(d.y, 0 + radius), height - radius);
    };
}

/**
 * Update the positions of nodes whenever we recieved a tick notification
 * from the force layout algorithm
 * @param {object} D3 tick event args
 */
function tick(e) {
	// Ensure that the Alpha never drops to 0
    // Note that on mobile devices this could be a big battery drain
    force.alpha(alpha);
      
    // Update the position of each node by applying a gravity function
    // and preventing any collisions of the nodes
    node.each(gravity(.51 * e.alpha))
        .each(function(d) { d.color = '#2B301C';})
        .each(function(d) { d.bounced = d.bounced || []; })
        .each(resolve_collisions(0.5))
		.attr("cx", function(d) { return d.x; })
		.attr("cy", function(d) { return d.y; })
        .style("stroke", function(d) { return d.color || '#2B301C'; });
};
    
function resolve_collisions(alpha) {
    
    // Build a quad tree from the nodes
    var quadtree = d3.geom.quadtree(nodes);
   
    // http://www.exeneva.com/2012/06/multiple-balls-bouncing-and-colliding-example/
    function bounce_vectors(d1, d2) {
        
        // Calculate the angle at which the balls collide
        var dx = d1.x - d2.x;
        var dy = (d1.y - d2.y) * -1;
        var collisionAngle = Math.atan2(dy, dx);
        
        // Calculate the speed of d1/d2
        var speed1 = Math.sqrt((d1.speedX * d1.speedX) + (d1.speedY * d1.speedY));
        var speed2 = Math.sqrt((d2.speedX * d2.speedX) + (d2.speedY * d2.speedY));
          
        // Get angles (in radians) for each ball, given current velocities
        var direction1 = Math.atan2(d1.speedY, d1.speedX);
        var direction2 = Math.atan2(d2.speedY, d2.speedX);
       
        // Rotate velocity vectors so we can plug into equation for conservation of momentum
        var rotatedVelocityX1 = speed1 * Math.cos(direction1 - collisionAngle);
        var rotatedVelocityY1 = speed1 * Math.sin(direction1 - collisionAngle);
        var rotatedVelocityX2 = speed2 * Math.cos(direction2 - collisionAngle);
        var rotatedVelocityY2 = speed2 * Math.sin(direction2 - collisionAngle);        
        
      // Update actual velocities using conservation of momentum
        var finalVelocityX1 = ((d1.size - d2.size) * rotatedVelocityX1 + (d2.size + d2.size) * rotatedVelocityX2) / (d1.size + d2.size);
        var finalVelocityX2 = ((d1.size + d1.size) * rotatedVelocityX1 + (d2.size - d1.size) * rotatedVelocityX2) / (d1.size + d2.size);
        
        // Y velocities remain constant
        var finalVelocityY1 = rotatedVelocityY1;
        var finalVelocityY2 = rotatedVelocityY2;
        
        // Rotate angles back again so the collision angle is preserved
        d1.speedX = Math.cos(collisionAngle) * finalVelocityX1 + Math.cos(collisionAngle + Math.PI/2) * finalVelocityY1;
        d1.speedY = Math.sin(collisionAngle) * finalVelocityX1 + Math.sin(collisionAngle + Math.PI/2) * finalVelocityY1;
        d2.speedX = Math.cos(collisionAngle) * finalVelocityX2 + Math.cos(collisionAngle + Math.PI/2) * finalVelocityY2;
        d2.speedY = Math.sin(collisionAngle) * finalVelocityX2 + Math.sin(collisionAngle + Math.PI/2) * finalVelocityY2;
    };
    
    return function(d, i) {
       
       
        

        
        // Using a quad tree to speed things up visit each pair of nodes
        quadtree.visit(function(quad, x1, y1, x2, y2) {
                     
            // Compare points together - but don't compare the
            // current point to itself
            if (quad.point && (quad.point !== d)) {
                                     
                // Calculate the distance between the centres of 
                // two nodes, we don't have take the square root
                // as we don't need it, and it's an expensive operation
                var x = d.x - quad.point.x;
                var y = d.y - quad.point.y;
                var distance = x * x + y * y;
            
                // Calculate the radius of each to determine how
                // close they need to be to be considered touching
                var radii = getSize(d) + getSize(quad.point);

                // If the nodes appear to be touching then we need to move the points
                if (distance < (radii * radii)) {
                    
                    // If these items have already bounced then continue
                    if(d.bounced.indexOf(quad.point) !== -1) {
                        return;
                    }
                    
                    d.bounced.push(quad.point);
                    quad.point.bounced.push(d);
                    setTimeout(function() {
                        var index = d.bounced.indexOf(quad.point);
                        d.bounced.splice(index, 1);
                        index = quad.point.bounced.indexOf(d);
                        quad.point.bounced.splice(index);
                    }, 5250);
                    
                    d.color = 'red';
                    quad.point.color = 'red';
                    bounce_vectors(d, quad.point);
            }
      }
    });
  };
}



	force.start();		   		

};

d3.Orbs();
.node {
    fill: #77B72B;
    stroke: #2B301C;
    stroke-width: 3px;
}
svg {
    background: #222;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

0 个答案:

没有答案