1. I'm not sure if it would be better to make the envelope into an extension of the Rectangle constructor.
  2. The physics seem a bit dodgy, it works well most of the time, but sometimes the boxes overlap and sometimes the bouncing doesn't work properly.

var envelopeHeight = 100;
var fallRange = 8.5;
var fallMidpoint = 75;
var maxOnScreen = 6;
var frequency = 4500; //time between falls
var initialVelocity = 0.2;

var gravity = 0.6;
var friction = 0.97;
var dampening = 2;

var lastEnvelope = 0;
var envelopes = [];

function onFrame() {
    var onScreen = 0;
    for (var i = 0; i < envelopes.length; i++) {
        if (!envelopes[i].dead) {
            onScreen++;
        }
        envelopes[i].updateSelf();
    }
    if (Date.now() > lastEnvelope + frequency && onScreen <= maxOnScreen) {
        var dropX = view.bounds.width * (((Math.random() * fallRange) + fallMidpoint) / 100);
        var envelope = new Path.Rectangle(new Point(dropX, -envelopeHeight), 100, envelopeHeight);
        envelope.position = new Point(dropX, -envelopeHeight);
        envelope.fillColor = {
            hue: Math.random() * 360,
            saturation: 1,
            brightness: 1
        }
        envelope.velocity = initialVelocity;
        envelope.removed = false;
        envelope.updateSelf = function() {
            if (this.bounds.bottom > view.bounds.height + 1000) {
                this.remove();
                envelopes.splice(i,1);
                return;
            };
            function bounce() {
                if (this.velocity > dampening) {
                    this.velocity -= dampening;
                } else {
                    this.velocity = 0;
                }
                this.velocity = -this.velocity;
            };          

            if (this.bounds.bottom >= view.bounds.height && !this.dead) {
                bounce.apply(this);
            }

            for (var i = 0;i<envelopes.length;i++) {
                var bottomLeft = new Point(this.bounds.left, this.bounds.bottom);
                var bottomRight = new Point(this.bounds.left + this.bounds.width, this.bounds.bottom);
                if (envelopes[i].hitTest(bottomLeft) && envelopes[i].id!=this.id) {
                    bounce.apply(this);
                };
                if (envelopes[i].hitTest(bottomRight) && envelopes[i].id!=this.id) {
                    bounce.apply(this);
                };
            }

            this.position.y += this.velocity;
            this.velocity += gravity;
            this.velocity *= friction;          
        }
        envelopes.push(envelope);
        lastEnvelope = Date.now();
    };
};

function onMouseDown() {
    for (var i = 0;i<envelopes.length;i++) {
        envelopes[i].dead = true;
    }
}

     var envelopeHeight = 100;
        var fallRange = 8.5;
        var fallMidpoint = 75;
        var maxOnScreen = 6;
        var frequency = 4500; //time between falls
        var initialVelocity = 0.2;
        
        var gravity = 0.6;
        var friction = 0.97;
        var dampening = 2;
        
        var lastEnvelope = 0;
        var envelopes = [];
        
        function onFrame() {
        	var onScreen = 0;
        	for (var i = 0; i < envelopes.length; i++) {
        		if (!envelopes[i].dead) {
        			onScreen++;
        		}
        		envelopes[i].updateSelf();
        	}
        	if (Date.now() > lastEnvelope + frequency && onScreen <= maxOnScreen) {
        		var dropX = view.bounds.width * (((Math.random() * fallRange) + fallMidpoint) / 100);
        		var envelope = new Path.Rectangle(new Point(dropX, -envelopeHeight), 100, envelopeHeight);
        		envelope.position = new Point(dropX, -envelopeHeight);
        		envelope.fillColor = {
        			hue: Math.random() * 360,
        			saturation: 1,
        			brightness: 1
        		}
        		envelope.velocity = initialVelocity;
        		envelope.removed = false;
        		envelope.updateSelf = function() {
        			if (this.bounds.bottom > view.bounds.height + 1000) {
        				this.remove();
        				envelopes.splice(i,1);
        				return;
        			};
        			function bounce() {
        				if (this.velocity > dampening) {
        					this.velocity -= dampening;
        				} else {
        					this.velocity = 0;
        				}
        				this.velocity = -this.velocity;
        			};			
        
        			if (this.bounds.bottom >= view.bounds.height && !this.dead) {
        				bounce.apply(this);
        			}
        
        			for (var i = 0;i<envelopes.length;i++) {
        				var bottomLeft = new Point(this.bounds.left, this.bounds.bottom);
        				var bottomRight = new Point(this.bounds.left + this.bounds.width, this.bounds.bottom);
        				if (envelopes[i].hitTest(bottomLeft) && envelopes[i].id!=this.id) {
        					bounce.apply(this);
        				};
        				if (envelopes[i].hitTest(bottomRight) && envelopes[i].id!=this.id) {
        					bounce.apply(this);
        				};
        			}
        
        			this.position.y += this.velocity;
        			this.velocity += gravity;
        			this.velocity *= friction;			
        		}
        		envelopes.push(envelope);
        		lastEnvelope = Date.now();
        	};
        };
        
        function onMouseDown() {
        	for (var i = 0;i<envelopes.length;i++) {
        		envelopes[i].dead = true;
        	}
        }
#envelopes {
  height: 100%;
  width: 100%
}
<script src="http://cdnjs.cloudflare.com/ajax/libs/paper.js/0.9.18/paper-full.min.js"></script>

<script type="text/paperscript" canvas="envelopes" resize>
	     var envelopeHeight = 100;
        var fallRange = 8.5;
        var fallMidpoint = 75;
        var maxOnScreen = 6;
        var frequency = 4500; //time between falls
        var initialVelocity = 0.2;
        
        var gravity = 0.6;
        var friction = 0.97;
        var dampening = 2;
        
        var lastEnvelope = 0;
        var envelopes = [];
        
        function onFrame() {
        	var onScreen = 0;
        	for (var i = 0; i < envelopes.length; i++) {
        		if (!envelopes[i].dead) {
        			onScreen++;
        		}
        		envelopes[i].updateSelf();
        	}
        	if (Date.now() > lastEnvelope + frequency && onScreen <= maxOnScreen) {
        		var dropX = view.bounds.width * (((Math.random() * fallRange) + fallMidpoint) / 100);
        		var envelope = new Path.Rectangle(new Point(dropX, -envelopeHeight), 100, envelopeHeight);
        		envelope.position = new Point(dropX, -envelopeHeight);
        		envelope.fillColor = {
        			hue: Math.random() * 360,
        			saturation: 1,
        			brightness: 1
        		}
        		envelope.velocity = initialVelocity;
        		envelope.removed = false;
        		envelope.updateSelf = function() {
        			if (this.bounds.bottom > view.bounds.height + 1000) {
        				this.remove();
        				envelopes.splice(i,1);
        				return;
        			};
        			function bounce() {
        				if (this.velocity > dampening) {
        					this.velocity -= dampening;
        				} else {
        					this.velocity = 0;
        				}
        				this.velocity = -this.velocity;
        			};			
        
        			if (this.bounds.bottom >= view.bounds.height && !this.dead) {
        				bounce.apply(this);
        			}
        
        			for (var i = 0;i<envelopes.length;i++) {
        				var bottomLeft = new Point(this.bounds.left, this.bounds.bottom);
        				var bottomRight = new Point(this.bounds.left + this.bounds.width, this.bounds.bottom);
        				if (envelopes[i].hitTest(bottomLeft) && envelopes[i].id!=this.id) {
        					bounce.apply(this);
        				};
        				if (envelopes[i].hitTest(bottomRight) && envelopes[i].id!=this.id) {
        					bounce.apply(this);
        				};
        			}
        
        			this.position.y += this.velocity;
        			this.velocity += gravity;
        			this.velocity *= friction;			
        		}
        		envelopes.push(envelope);
        		lastEnvelope = Date.now();
        	};
        };
        
        function onMouseDown() {
        	for (var i = 0;i<envelopes.length;i++) {
        		envelopes[i].dead = true;
        	}
        }
       </script>

<canvas id="envelopes"></canvas>

up vote 11 down vote accepted
if (this.velocity > dampening) {
    this.velocity -= dampening;
} else {
    this.velocity = 0;
}
this.velocity = -this.velocity;

Seems like you could use Math functions for this:

this.velocity = Math.min(dampening - this.velocity, 0);

That will simplify it somewhat.

Logic behind it:

if (this.velocity > dampening) {
    this.velocity -= dampening;
} else {
    this.velocity = 0;
}

This can be converted to use Math.max, like so:

this.velocity = Math.max(this.velocity - dampening, 0);

Then we need to invert it. We can combine that by using Math.min:

this.velocity = Math.min((this.velocity - dampening)*-1, 0);

But as 200_success points out in the comments, you can swap the values so you don't need *-1:

this.velocity = Math.min(dampening - this.velocity, 0);

and that greatly simplifies your code. It could use a comment though; something like "inverts and reduces velocity". But you should do that anyway, on a method level.

There's this too:

for (var i = 0;i<envelopes.length;i++) {
    var bottomLeft = new Point(this.bounds.left, this.bounds.bottom);
    var bottomRight = new Point(this.bounds.left + this.bounds.width, this.bounds.bottom);
    if (envelopes[i].hitTest(bottomLeft) && envelopes[i].id!=this.id) {
        bounce.apply(this);
    };
    if (envelopes[i].hitTest(bottomRight) && envelopes[i].id!=this.id) {
        bounce.apply(this);
    };
}

You go through an expensive hitTest and then check if the id is not the same. And you do this twice. I'd add it as a separate case, before hitTesting.

for (var i = 0;i<envelopes.length;i++) {
    if(envelopes[i].id==this.id){
        continue;
    }
    var bottomLeft = new Point(this.bounds.left, this.bounds.bottom);
    var bottomRight = new Point(this.bounds.left + this.bounds.width, this.bounds.bottom);
    if (envelopes[i].hitTest(bottomLeft)) {
        bounce.apply(this);
    };
    if (envelopes[i].hitTest(bottomRight)) {
        bounce.apply(this);
    };
}

But it could use some more improvements. Why redeclare the same variables with the same values with each iteration?

var bottomLeft = new Point(this.bounds.left, this.bounds.bottom);
var bottomRight = new Point(this.bounds.left + this.bounds.width, this.bounds.bottom);
for (var i = 0;i<envelopes.length;i++) {
    if(envelopes[i].id==this.id){
        continue;
    }
    if (envelopes[i].hitTest(bottomLeft)) {
        bounce.apply(this);
    };
    if (envelopes[i].hitTest(bottomRight)) {
        bounce.apply(this);
    };
}

Now I can see something interesting: bounce inverts velocity and dampens it a bit. But you bounce whenever you hit something with your left corner... or with your right corner.

What happens if something falls straight on top? It falls flat/falls through!

var bottomLeft = new Point(this.bounds.left, this.bounds.bottom);
var bottomRight = new Point(this.bounds.left + this.bounds.width, this.bounds.bottom);
for (var i = 0;i<envelopes.length;i++) {
    if(envelopes[i].id==this.id){
        continue;
    }
    if (envelopes[i].hitTest(bottomLeft)) {
        bounce.apply(this);
    } else if (envelopes[i].hitTest(bottomRight)) {
        bounce.apply(this);
    };
}

There, fixed.

... hang on.

You bounce when you hit the bottom, huh.

But if you bounce there, then why are we checking for other bounces?

if (this.bounds.bottom >= view.bounds.height && !this.dead) {
    bounce.apply(this);
} else {
    var bottomLeft = new Point(this.bounds.left, this.bounds.bottom);
    var bottomRight = new Point(this.bounds.left + this.bounds.width, this.bounds.bottom);
    for (var i = 0;i<envelopes.length;i++) {
        if(envelopes[i].id==this.id){
            continue;
        }
        if (envelopes[i].hitTest(bottomLeft)) {
            bounce.apply(this);
        } else if (envelopes[i].hitTest(bottomRight)) {
            bounce.apply(this);
        };
    }
}

And it seems I missed a rather obvious clean up too...

if (this.bounds.bottom >= view.bounds.height && !this.dead) {
    bounce.apply(this);
} else {
    var bottomLeft = new Point(this.bounds.left, this.bounds.bottom);
    var bottomRight = new Point(this.bounds.left + this.bounds.width, this.bounds.bottom);
    for (var i = 0;i<envelopes.length;i++) {
        if(envelopes[i].id==this.id){
            continue;
        }
        if (envelopes[i].hitTest(bottomLeft) || envelopes[i].hitTest(bottomRight)) {
            bounce.apply(this);
        };
    }
}

I'm not that well versed in JavaScript so I have no idea what that semicolon is doing at the end of that if statement. If it's not doing anything useful, remove it.

So that was some refactoring. Find some duplication and pull on that thread until it shows you all the places it has been. Then sew it up again, but properly this time.

Final snippet (so you can see my changes work!):

var envelopeHeight = 100;
var fallRange = 8.5;
var fallMidpoint = 75;
var maxOnScreen = 6;
var frequency = 4500; //time between falls
var initialVelocity = 0.2;

var gravity = 0.6;
var friction = 0.97;
var dampening = 2;

var lastEnvelope = 0;
var envelopes = [];

function onFrame() {
    var onScreen = 0;
    for (var i = 0; i < envelopes.length; i++) {
        if (!envelopes[i].dead) {
            onScreen++;
        }
        envelopes[i].updateSelf();
    }
    if (Date.now() > lastEnvelope + frequency && onScreen <= maxOnScreen) {
        var dropX = view.bounds.width * (((Math.random() * fallRange) + fallMidpoint) / 100);
        var envelope = new Path.Rectangle(new Point(dropX, -envelopeHeight), 100, envelopeHeight);
        envelope.position = new Point(dropX, -envelopeHeight);
        envelope.fillColor = {
            hue: Math.random() * 360,
            saturation: 1,
            brightness: 1
        }
        envelope.velocity = initialVelocity;
        envelope.removed = false;
        envelope.updateSelf = function() {
            if (this.bounds.bottom > view.bounds.height + 1000) {
                this.remove();
                envelopes.splice(i, 1);
                return;
            };

            function bounce() {
                this.velocity = Math.min(dampening - this.velocity, 0);
            };
            if (this.bounds.bottom >= view.bounds.height && !this.dead) {
                bounce.apply(this);
            } else {
                var bottomLeft = new Point(this.bounds.left, this.bounds.bottom);
                var bottomRight = new Point(this.bounds.left + this.bounds.width, this.bounds.bottom);
                for (var i = 0; i < envelopes.length; i++) {
                    if (envelopes[i].id == this.id) {
                        continue;
                    }
                    if (envelopes[i].hitTest(bottomLeft) || envelopes[i].hitTest(bottomRight)) {
                        bounce.apply(this);
                    };
                }
            }

            this.position.y += this.velocity;
            this.velocity += gravity;
            this.velocity *= friction;
        }
        envelopes.push(envelope);
        lastEnvelope = Date.now();
    };
};

function onMouseDown() {
    for (var i = 0; i < envelopes.length; i++) {
        envelopes[i].dead = true;
    }
}
#envelopes {
  height: 100%;
  width: 100%
}
<script src="http://cdnjs.cloudflare.com/ajax/libs/paper.js/0.9.18/paper-full.min.js"></script>

<script type="text/paperscript" canvas="envelopes" resize>
var envelopeHeight = 100;
var fallRange = 8.5;
var fallMidpoint = 75;
var maxOnScreen = 6;
var frequency = 4500; //time between falls
var initialVelocity = 0.2;

var gravity = 0.6;
var friction = 0.97;
var dampening = 2;

var lastEnvelope = 0;
var envelopes = [];

function onFrame() {
    var onScreen = 0;
    for (var i = 0; i < envelopes.length; i++) {
        if (!envelopes[i].dead) {
            onScreen++;
        }
        envelopes[i].updateSelf();
    }
    if (Date.now() > lastEnvelope + frequency && onScreen <= maxOnScreen) {
        var dropX = view.bounds.width * (((Math.random() * fallRange) + fallMidpoint) / 100);
        var envelope = new Path.Rectangle(new Point(dropX, -envelopeHeight), 100, envelopeHeight);
        envelope.position = new Point(dropX, -envelopeHeight);
        envelope.fillColor = {
            hue: Math.random() * 360,
            saturation: 1,
            brightness: 1
        }
        envelope.velocity = initialVelocity;
        envelope.removed = false;
        envelope.updateSelf = function() {
            if (this.bounds.bottom > view.bounds.height + 1000) {
                this.remove();
                envelopes.splice(i, 1);
                return;
            };

            function bounce() {
                this.velocity = Math.min(dampening - this.velocity, 0);
            };
            if (this.bounds.bottom >= view.bounds.height && !this.dead) {
                bounce.apply(this);
            } else {
                var bottomLeft = new Point(this.bounds.left, this.bounds.bottom);
                var bottomRight = new Point(this.bounds.left + this.bounds.width, this.bounds.bottom);
                for (var i = 0; i < envelopes.length; i++) {
                    if (envelopes[i].id == this.id) {
                        continue;
                    }
                    if (envelopes[i].hitTest(bottomLeft) || envelopes[i].hitTest(bottomRight)) {
                        bounce.apply(this);
                    };
                }
            }

            this.position.y += this.velocity;
            this.velocity += gravity;
            this.velocity *= friction;
        }
        envelopes.push(envelope);
        lastEnvelope = Date.now();
    };
};

function onMouseDown() {
    for (var i = 0; i < envelopes.length; i++) {
        envelopes[i].dead = true;
    }
}
       </script>

<canvas id="envelopes"></canvas>

I can't get the indentation to play nice though. You might have to resolve that issue on your own.

  • 5
    (this.velocity - dampening)*-1 is just dampening - this.velocity. – 200_success Sep 11 '14 at 8:29
  • @200_success Damn I didn't notice that. – Pimgd Sep 11 '14 at 8:31

Your Answer

 
discard

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

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