ZIM BITS TUTORIAL
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>ZIM BITS - Physics and Linkages with JavaScript HTML 5 Canvas and CreateJS - Tips, Techniques and Tutorials</title>
<!-- Welcome to ZIM at https://zimjs.com - Code Creativity! -->
<!-- ZIM runs on the HTML Canvas powered by JavaScript and CreateJS https://createjs.com -->
<!-- Founded by Inventor Dan Zen - http://danzen.com - Canadian New Media Award Winner -->
<!-- ZIM is free to use. You can donate to help improve ZIM at http://zimjs.com/donate -->
<script src="https://zimjs.org/cdn/1.3.4/createjs.js"></script>
<script src="https://zimjs.org/cdn/01/zim_doc.js"></script>
<!-- use zimjs.com/distill for minified individual functions! -->
<!-- physics libraries -->
<script src="https://zimjs.org/cdn/Box2dWeb-2.1.a.3.min.js"></script>
<script src="https://zimjs.org/cdn/physics_2.1.js"></script><!-- helper code for box2D -->
<script src="https://zimjs.com/bits/footer10.js"></script><!-- you will not need this -->
<script>
// SCALING OPTIONS
// scaling can have values as follows with full being the default
// FIT sets canvas and stage to dimensions and scales to fit inside window size
// FILL sets canvas and stage to dimensions and scales to fit outside window size
// FULL sets stage to window size with no scaling
// "tagID" add canvas to HTML tag of ID - set to dimensions if provided - no scaling
const scaling = FIT; // this will resize to fit inside the screen dimensions
const width = 1000;
const height = 800;
const color = dark; // or any HTML color such as "violet" or "#333333"
const outerColor = darker;
new Frame(scaling, width, height, color, outerColor, ready);
function ready() {
// given F (Frame), S (Stage), W (width), H (height)
// ZIM BITS - Physics (PART 2) with Box2D (2016 - updated 2022)
// ZIM can be used with 2D physics engines such as Box2D
// Box2D is rediculously verbose - but that gives it flexibility
// the ZIM Physics module offers some welcome abstraction
// (this code would be about 10 times as many lines)
// you can still use traditional Box2D as well
// currently there are a couple ZIM Bits on Physics
// this one shows forces, buoyancy, and sensors
// the first one shows basic shapes, mapping and mouse interaction
// OVERVIEW
// in general, we set up virtual Box2D shapes
// and then Box2D calculates forces, positons, rotation, collisions
// we can see these if we set the debug to true
// we then map our own ZIM and CreateJS assets onto the Box2D ones
// you are not supposed to directly influence positions
// but rather use forces and let Box2D move things
// SPECIFICS
// here we create a b2BuoyancyController
// and add bodies to the controller when we enter a sensor
// we remove the body when we exit the sensor
// the sensor is shaped like the bowl
// we add shapes to start and on click and on keypress
// we spin the shape with an impulse force randomly off centered
// STEPS
// 1. load bowl assets with F.loadAssets
// 2. when loaded make the bowl, some steam and a food container
// 3. load in the ingredients into an array
// 4. once one ingredient is in the array start the app
// 5. define the borders
// 6. make a physics.World and pass in the frame and borders
// 7. remove borders we do not need
// 8. create a b2BuoyancyController with desired properties
// 9. in an interval add a bunch of ingredients
// 10. make the addIngredient function work on mouse and key down
// 11. make a ZIM ingredient from a shuffled array
// 12. position and rotate the body
// 12. addPhysics to match shape
// 14. spin the body with an impulse force
// 15. add the centerReg ZIM ingredient to the food container
// 16. map the ZIM ingredient to the Box2D body
// 17. use traditional Box2D to make a custom bowl body
// 18. set the bowl as a sensor (will no longer physically interact)
// 19. create physics rectangles for edges of bowl
// 20. add a contact listener to add body to b2BuoyancyController
// 21. add a contact listener to remove body from b2BuoyancyController
// 22. set optional mouse dragging
// 23. set optional debug canvas showing Box2D shapes
// 1. load bowl assets with F.loadAssets (or load this into the Frame above)
const assetPath = "assets/soup/";
F.loadAssets(["bowlBacking.png", "bowl.png", "steam.png"], assetPath);
// the true on the end makes sure that bowlReady is only loaded once
// and then this complete listener is removed
// this is important because we load more assets later
// and if we did not remove this listener, it would run again
// for the second set of assets loaded
F.on("complete", bowlReady, null, true);
function bowlReady() {
// 2. when loaded make the bowl, some steam and a food container
const air = new Rectangle(W, H*.65, yellow)
.alp(.95)
.addTo();
const bowlBacking = asset("bowlBacking.png")
.sca(.76)
.centerReg()
.mov(0, 118);
const steam = asset("steam.png")
.noMouse()
.sca(.70)
.centerReg()
.mov(0, -240)
.animate({
obj:{alpha:0},
time:3,
loop:true,
rewind:true
});
const food = new Container(W,H).addTo();
// adding a mousedown to the food
// stops the mousedown going through to the air
// which would add another ingredient
// we don't want to add an ingredient when dragging food
food.on("mousedown",function(){});
const steam2 = steam.clone()
.sca(.70)
.centerReg()
.noMouse()
.mov(0, -150)
.alp(0)
.rot(180)
.animate({
props:{alpha:1},
time:3,
loop:true,
rewind:true
});
const bowl = asset("bowl.png")
.sca(.76)
.centerReg()
.alp(.8)
.mov(0, 118);
F.loadAssets([
"carrot1.png", "carrot2.png",
"mushroom1.png", "mushroom2.png", "mushroom3.png",
"cellery1.png", "cellery2.png", "cellery3.png",
], assetPath);
const ingredients = [];
F.on("assetload", function(e){
// 3. load in the ingredients into an array
ingredients.push(e.asset);
// 4. once one ingredient is in the array start the app
// add the asset to the ingredient list
// and if this is the first asset, it goes through to start the app
// otherwise if it is a subsequent ingredient it exits the function
if (ingredients.length>1) return;
// 5. define the borders
const fromBottom = 150;
const borders = new Boundary(0, 0, W, H-fromBottom);
// 6. make a physics.World and pass in the frame and borders
const physics = new Physics(10, borders);
const world = physics.world;
const scale = physics.scale;
// 7. remove borders we do not need
// could have left the sides in but decided not to
// but must remove the top to let the ingredients fall in
physics.remove(physics.borderTop);
physics.remove(physics.borderLeft);
physics.remove(physics.borderRight);
// 8. create a b2BuoyancyController with desired properties
const bc = new b2BuoyancyController();
bc.normal.Set(0,-1);
bc.offset = -H/2/physics.scale;
bc.density = 3;
bc.linearDrag = 3;
bc.angularDrag = 2;
world.AddController(bc);
// 9. in an interval add a bunch of ingredients
let count = 0;
const inter = interval(.3, function() {
count++;
addIngredient(rand(300, W-300));
if (count > 8) inter.clear();
});
// 10. make the addIngredient function work on mouse and key down
air.on("mousedown", addIngredient);
F.on("keydown", addIngredient);
const s = .4; // ingredient scale
const reduce = .9; // make hit area a little smaller
function addIngredient(locat) {
// 11. make a ZIM ingredient from a shuffled array
// we randomize array and then pick first element
// could also use ingredients[rand(ingredients.length-1)]
const ing = shuffle(ingredients)[0]
.clone()
.centerReg()
.sca(.4)
// 12. position and rotate the object
ing.x = (typeof locat == "number") ? locat : F.mouseX+rand(-5,5);
ing.y = -100;
ing.rotation = rand(360);
// 13. based on the ingredient, addPhysics
if (ing.file.match(/mushroom/i)) {
// dynamic, contract, shape, friction, linear, angular, density, bounciness, maskBits, categoryBits, physics, restitution
// body = physics.makeCircle(ing.width*s/2*reduce, true, 1, 2, 2);
ing.addPhysics(true, 10, "circle", 1, null, 2, 2)
} else if (ing.file.match(/carrot/i)) {
// body = physics.makeCircle(ing.width*s/2, true, 1, 1, 2.4);
ing.addPhysics(true, 10, "circle", 1, null, 1, 2.4)
} else {
// body = physics.makeRectangle(ing.width*s*reduce, ing.height*s*reduce, true, 1, 2, 2.7);
ing.addPhysics(true, 10, null, 1, null, 2, 2.7)
}
ing.bot().ord(3);
// 14. spin the body with an impulse force
// will push in the y direction a random positive or negative force
// that is between 5 and 10 and 600 off to the side
// this is because this version of Box2D does not have impulseTorque
// const force = (rand(1)==0?1:-1)*rand(5,10);
// ing.impulse(0,force), body.GetWorldPoint(new b2Vec2(600,0)));
ing.spin(rand(.5,1.5,false,true));
}
// MAKING BOWL
// 17. use traditional Box2D to make a custom bowl body
// fun wow! This is what the ZIM physics.js is abstracing
const bowlDef = new b2BodyDef();
bowlDef.type = b2Body.b2_staticBody;
const bowlBody = world.CreateBody(bowlDef);
const bowlShape = new b2PolygonShape();
const points = [];
const bTopW = 500;
const bBotW = 400;
const bHeight = H/2-fromBottom;
points[0] = new b2Vec2(0/scale, 0/scale);
points[3] = new b2Vec2(bTopW/scale, 0/scale);
points[2] = new b2Vec2((bTopW-(bTopW-bBotW)/2)/scale, bHeight/scale);
points[1] = new b2Vec2((bTopW-bBotW)/2/scale, bHeight/scale);
bowlBody.x = (W-bTopW)/2;
bowlBody.y = H/2;
bowlBody.SetPosition(new b2Vec2(bowlBody.x/scale,bowlBody.y/scale));
bowlShape.SetAsArray(points, points.length);
const bowlFixture = new b2FixtureDef();
// 18. set the bowl as a sensor (will no longer physically interact)
bowlFixture.isSensor = true
bowlFixture.shape = bowlShape;
bowlBody.CreateFixture(bowlFixture);
// 19. create rectangles for edges of bowl
// includes various fudge values
// use debug mode to get physics
// then stretch bowl graphic in image editor (i.e. photoshop)
const bowlLeft = new Rectangle(10, bHeight*1.2)
.centerReg()
.alp(0)
.loc(bowlBody.x + (bTopW-bBotW)/2/2 - 6, bowlBody.y + bHeight/2 - 22)
.rot(-11)
.addPhysics(false);
const bowlRight = new Rectangle(10, bHeight*1.2)
.centerReg()
.alp(0)
.loc(bowlBody.x + bTopW - (bTopW-bBotW)/2/2 + 6, bowlBody.y + bHeight/2 - 22)
.rot(11)
.addPhysics(false)
// CONTACT LISTENERS -- // also see contact() method of physics object - for easier code
// 20. add a contact listener to add body to b2BuoyancyController
// there is one contact listener for all contacts
// first make a b2ContactListener object
// then add the arranged BeginContact callback function
// this will be called when an object comes in contact
// there are two objects that contact m_fixtureA and m_fixtureB
// we make sure that one is a sensor
// and then the other one we add to the b2BuoyancyController
// if we wanted to we could have SetUserData to the bodies
// if we needed to know what type of body made contact
// but here, it does not matter
const contactListener = new b2ContactListener();
contactListener.BeginContact = function(e) {
if (e.m_fixtureA.m_isSensor || e.m_fixtureB.m_isSensor) {
if (e.m_fixtureA.m_isSensor) {
bc.AddBody(e.m_fixtureB.GetBody());
} else {
bc.AddBody(e.m_fixtureA.GetBody());
}
}
}
// 21. add a contact listener to remove body from b2BuoyancyController
// if a body is leaving contact with our sensor
// then remove the body from the b2BuoyancyController
contactListener.EndContact = function(e) {
if (e.m_fixtureA.m_isSensor || e.m_fixtureB.m_isSensor) {
if (e.m_fixtureA.m_isSensor) {
bc.RemoveBody(e.m_fixtureB.GetBody());
} else {
bc.RemoveBody(e.m_fixtureA.GetBody());
}
}
}
// set the contact listener on the world
world.SetContactListener(contactListener);
// MOUSE
// 22. set optional mouse dragging
physics.drag();
// DEBUG
// 23. set optional debug canvas showing Box2D shapes
// optionally see the BOX 2D debug canvas
// physics.debug();
}); // end first ingredient added
} // end bowlReady
const docItems = "Frame,Container,Rectangle,drag,animate,mov,alp,rot,sca,addTo,centerReg,shuffle,rand,interval,zog";
makeFooter(S, W, H, null, docItems); // ZIM BITS footer - you will not need this
} // end of ready
</script>
<meta name="viewport" content="width=device-width, user-scalable=no" />
</head>
<body>
<!-- canvas with id="myCanvas" is made by zim Frame -->
</body>
</html>