(() => { // plugins Matter.use(MatterAttractors); // constants const PATHS = { DOME: '0 0 0 300 17 250 20 231.9 25.7 199.1 36.9 161.7 53.3 129.5 74.6 100.2 100.2 74.6 129.5 53.3 161.7 56.9 96.1 25.7 231.9 20 268.1 20 303.9 25.7 338.3 36.9 370.5 53.3 399.8 74.6 425.4 100.2 446.7 129.5 463.1 161.7 474.3 196.1 480 211.9 480 250 500 300 500 0 0 0', // DROP_LEFT: '0 0 20 0 70 100 20 150 0 150 0 0', // DROP_RIGHT: '50 0 68 0 68 150 50 150 0 100 50 0', // APRON_LEFT: '0 0 180 120 0 120 0 0', // APRON_RIGHT: '180 0 180 120 0 120 180 0' }; const COLOR = { // BACKGROUND: clearRect(0,0,canvas.width,canvas.height), OUTER: '#F00', INNER: '#FFFFFF', BUMPER: '#FFFFFF', BUMPER_LIT: '#F00', PADDLE: '#FFFFFF', PINBALL: '#F00', LINK: '#F00', }; const GRAVITY = 1; const WIREFRAMES = false; const BUMPER_BOUNCE = 1.5; const MAX_VELOCITY = 50; // matter.js has a built in random range function, but it is deterministic function rand(min, max) { return Math.random() * (max - min) + min; } // shared variables let engine, world, render, pinball, stopperGroup; function load() { init(); createStaticBodies(); createPinball(); createEvents(); } function init() { // engine (shared) engine = Matter.Engine.create(); // world (shared) world = engine.world; world.bounds = { min: { x: 0, y: 0 }, max: { x: 500, y: 800 } }; world.gravity.y = GRAVITY; // simulate rolling on a slanted table // render (shared) render = Matter.Render.create({ element: $('.container')[0], engine: engine, options: { width: world.bounds.max.x, height: world.bounds.max.y, wireframes: WIREFRAMES, background: COLOR.BACKGROUND } }); Matter.Render.run(render); // runner let runner = Matter.Runner.create(); Matter.Runner.run(runner, engine); // used for collision filtering on various bodies stopperGroup = Matter.Body.nextGroup(true); // based on https://stackoverflow.com/questions/28324303/matter-js-mouse-click-on-body // https://github.com/liabru/matter-js/blob/081645474c4aa798e5b1ede5d5230b0ecb8835d2/examples/events.js var mouse = Matter.Mouse.create(render.canvas); var mouseConstraint = Matter.MouseConstraint.create(engine, { mouse: mouse, constraint: { stiffness: 1, render: { visible: false } } }) Matter.World.add(world, mouseConstraint); Matter.Events.on(engine, 'tick', function(event) { if (mouseConstraint.mouse.button == 0) { // console.log("click", mouseConstraint.body); if (mouseConstraint.body && mouseConstraint.body.url) { // console.log("opening link", mouseConstraint.body.url); function openTab(url) { // Create link in memory var a = window.document.createElement("a"); //a.target = '_blank'; a.href = url; // Dispatch fake click var e = window.document.createEvent("MouseEvents"); e.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); a.dispatchEvent(e); }; openTab(mouseConstraint.body.url); //window.open(mouseConstraint.body.url, "_self"); } } }); // an example of using mouse events on a mouse Matter.Events.on(mouseConstraint, 'mousedown', function(event) { var mousePosition = event.mouse.position; // console.log('mousedown at ' + mousePosition.x + ' ' + mousePosition.y); }); } //1 Placenotfound *activate the walls with it var BUMPER_DATA = [ { x: 100, y: 200, url: "./hotspots/1/index.html", text: "probably it's Page Not Found" }, //1 { x: 250, y: 200, url: "./hotspots/2/index.html", text: "probably it's the Lobster Lounge" }, //2 { x: 250, y: 380, url: "./hotspots/3/index.html", text: "probably it's the Secret Garden" }, //3 { x: 100, y: 380, url: "./hotspots/4/index.html", text: "probably it's Sonic the Fallout" }, //4 { x: 400, y: 380, url: "./hotspots/5/index.html", text: "probably it's Pigeons Plaza" }, //5 ]; function createStaticBodies() { Matter.World.add(world, [ // table boundaries (top, bottom, left, right) boundary(50, -30, 500, 100), boundary(250, 830, 500, 100), boundary(-40, 400, 100, 800), boundary(540, 400, 100, 800), // dome path(251, 0, PATHS.DOME), //The secret garden // wall(250, 200, 30, 110, COLOR.INNER), // wall(100, 380, 30, 110, COLOR.INNER), // wall(100, 380, 30, 110, COLOR.INNER), // wall(400, 380, 30, 110, COLOR.INNER), // wall(130, 560, 30, 110, COLOR.INNER, -0.96), // wall(370, 560, 30, 110, COLOR.INNER, 0.96), //Sonic the Sellout // Pigeons Plaza //wall(250, 380, 30, 110, COLOR.INNER, 0.96), // PNF wall(250, 560, 30, 110, COLOR.INNER), wall(175, 290, 30, 110, COLOR.INNER), wall(325, 290, 30, 110, COLOR.INNER), wall(130, 560, 30, 110, COLOR.INNER, -0.96), wall(370, 560, 30, 110, COLOR.INNER, 0.96), // Lobster Lounge //wall(455, 520, 7, 560, COLOR.OUTER), //wall(250, 380, 30, 110, COLOR.INNER), //wall(100, 380, 30, 110, COLOR.INNER), //wall(400, 380, 30, 110, COLOR.INNER), //wall(130, 200, 30, 110, COLOR.INNER, -0.96), //wall(370, 560, 30, 110, COLOR.INNER, 0.96), // reset zones (center, right) reset(221, 420), reset(470, 40) ]); BUMPER_DATA.forEach(d => { Matter.World.add(world, bumper(d.x, d.y, d.url)); }); } function createPinball() { // x/y are set to when pinball is launched pinball = Matter.Bodies.circle(0, 0, 12, { label: 'pinball', collisionFilter: { group: stopperGroup }, render: { fillStyle: COLOR.PINBALL } }); Matter.World.add(world, pinball); launchPinball(); } function createEvents() { // events for when the pinball hits stuff Matter.Events.on(engine, 'collisionStart', function(event) { let pairs = event.pairs; pairs.forEach(function(pair) { if (pair.bodyB.label === 'pinball') { switch (pair.bodyA.label) { case 'reset': launchPinball(); break; case 'bumper': pingBumper(pair.bodyA); break; } } }); }); // regulate pinball Matter.Events.on(engine, 'beforeUpdate', function(event) { // bumpers can quickly multiply velocity, so keep that in check Matter.Body.setVelocity(pinball, { x: Math.max(Math.min(pinball.velocity.x, MAX_VELOCITY), -MAX_VELOCITY), y: Math.max(Math.min(pinball.velocity.y, MAX_VELOCITY), -MAX_VELOCITY), }); // cheap way to keep ball from going back down the shooter lane if (pinball.position.x > 450 && pinball.velocity.y > 0) { Matter.Body.setVelocity(pinball, { x: 0, y: -10 }); } }); } function launchPinball() { Matter.Body.setPosition(pinball, { x: 465, y: 765 }); Matter.Body.setVelocity(pinball, { x: 0, y: -25 + rand(-2, 2) }); Matter.Body.setAngularVelocity(pinball, 0); } function pingBumper(bumper) { //updateScore(currentScore + 10); // flash color bumper.render.fillStyle = COLOR.BUMPER_LIT; setTimeout(function() { bumper.render.fillStyle = COLOR.BUMPER; }, 100); // ass = ['assets/video_2021-09-08_17-23-49.mp4', 'assets/IMG_4533.mp4', 'assets/IMG_4532.mp4', 'assets/video_2021-09-08_17-24-23.mp4', 'assets/IMG_4454.MOV', 'assets/IMG_2384.MOV', 'assets/video_2021-09-08_17-24-20.mp4', 'assets/video_2021-09-08_17-24-09.mp4', 'assets/simu_124 (converti).mov', 'assets/video.mp4', 'assets/IMG_2159.MOV', 'assets/video_2021-09-08_17-24-16.mp4', 'assets/video_2021-09-08_17-23-22.mp4', 'assets/video_2021-09-08_17-24-14.mp4', 'assets/video_2021-09-08_17-24-00.mp4', 'assets/IMG_2177.MOV', 'assets/video_2021-09-08_17-24-04.mp4', 'assets/video_2021-09-08_17-24-07.mp4', 'assets/IMG_2161.MOV', 'assets/sudoreboot.mov', 'assets/video_2021-09-08_17-23-56.mp4', 'assets/video_2021-09-08_17-23-53.mp4', 'assets/simu_54.mov', 'assets/video_2021-09-08_17-23-45.mp4'] ass = ['video_2021-09-08_17-23-49.mp4', 'IMG_2384.mp4', 'IMG_4454.mp4', 'simu_124 (converti).mp4', 'IMG_4533.mp4', 'IMG_4532.mp4', 'video_2021-09-08_17-24-23.mp4', 'video_2021-09-08_17-24-20.mp4', 'video_2021-09-08_17-24-09.mp4', 'video.mp4', 'video_2021-09-08_17-24-16.mp4', 'video_2021-09-08_17-23-22.mp4', 'video_2021-09-08_17-24-14.mp4', 'video_2021-09-08_17-24-00.mp4', 'sudoreboot.mp4', 'IMG_4533dsf.mp4', 'video_2021-09-08_17-24-04.mp4', 'video_2021-09-08_17-24-07.mp4', 'simu_54.mp4', 'IMG_2159.mp4', 'video_2021-09-08_17-23-56.mp4', 'video_2021-09-08_17-23-53.mp4', 'IMG_2161.mp4', 'IMG_2177.mp4', 'video_2021-09-08_17-23-45.mp4'] if (bumper) { image = document.getElementById('asset'); image.src = 'assets/' + ass[Math.floor(Math.random() * 23)]; console.log(image.src) document.querySelector('video').load(); document.querySelector('video').play(); } // swap text (#textfeature) var textcontainer = document.getElementById("but"); // console.log('BUMPER:', bumper) BUMPER_DATA.forEach(b => { if (b.url == bumper.url) { // console.log('b.url == bumper.url :', b.url, bumper.url); // console.log('b.text:', b.text); textcontainer.innerText = b.text; } }); } // outer edges of pinball table function boundary(x, y, width, height) { return Matter.Bodies.rectangle(x, y, width, height, { isStatic: true, render: { fillStyle: COLOR.OUTER } }); } // wall segments function wall(x, y, width, height, color, angle = 0) { return Matter.Bodies.rectangle(x, y, width, height, { angle: angle, isStatic: true, chamfer: { radius: 15 }, render: { fillStyle: color } }); } // bodies created from SVG paths function path(x, y, path) { let vertices = Matter.Vertices.fromPath(path); return Matter.Bodies.fromVertices(x, y, vertices, { isStatic: true, render: { fillStyle: COLOR.OUTER, // add stroke and line width to fill in slight gaps between fragments strokeStyle: COLOR.OUTER, lineWidth: 1 } }); } // round bodies that repel pinball function bumper(x, y, url) { let bumper = Matter.Bodies.circle(x, y, 25, { label: 'bumper', url: url, isStatic: true, render: { fillStyle: COLOR.BUMPER } }); // for some reason, restitution is reset unless it's set after body creation bumper.restitution = BUMPER_BOUNCE; return bumper; } // invisible bodies to constrict paddles // contact with these bodies causes pinball to be relaunched function reset(x, width) { return Matter.Bodies.rectangle(x, 781, width, 4, { label: 'reset', isStatic: true, render: { fillStyle: '#F00' } }); } window.addEventListener('load', load, false); })();