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>