import zim from "https://zimjs.org/cdn/016/zim_three";
import phy from "https://zimjs.org/cdn/016/zim_physics";

// see https://zimjs.com
// and https://zimjs.com/learn
// and https://zimjs.com/docs

new Frame(FIT, 3000, 500, black, black, ready, ["maze.png", "gf_Fascinate+Inline"], "https://assets.codepen.io/2104200/");
function ready() {
	
	// given F (Frame), S (Stage), W (width), H (height)
	// put code here

	// Message - TextureActive to show Title and Button
	// Parameters:
	// width, height, color, color2, angle, borderColor, borderWidth, corner, interactive, animated, backingOrbit, pattern, scalePattern
	const message = new TextureActive({color:black, width:400, height:90});

	new Label("RING MAZE", 40, "Fascinate Inline", pink)
		.pos(0,0,CENTER,TOP,message)
		.noMouse();
	const button = new Button({label:"PLAY", corner:6}).sca(.5).pos(0,0,CENTER,BOTTOM,message).tap(()=>{		
		button.removeFrom();
		timeout(1, ()=>{
			ball.dynamic = true
		})
	});
	STYLE = {once:true, italic:true, bold:true}
	const win = new Label("CONGRATULATIONS",20,null,yellow)
		.reg(CENTER)
		.pos(0,0,CENTER,BOTTOM,message)
		.cache()
		.sca(0);
	
	// ZIM Link
	const madeWith = new TextureActive(400,300,black).addTo();
    TextureActive
		.makeBacking(madeWith.width, madeWith.height, "")
		.addTo(madeWith)
		.tap(()=>{zgo("https://zimjs.com/studio", "_blank")});
	
	
	// Cylinder wall
	const wall = new TextureActive({color:black, width:W, height:H, corner:0});

	// we do not want the content to get in the way of the OrbitControls
	// so easiest to add to container and set noMouse() - we can still track mouse position in Ticker
	const holder = new Container(W,H).addTo(wall).noMouse();
	
	// MAZE
	// we can load in ANY picture of a maze as long as the walls are different than the backing
	// we could even load two pictures... a hidden one to represent the walls and a visual more complex one
	// we then use physics to apply a force to the ball to follow the mouse
	// and we make physic walls dynamically around the ball's position
	// the walls are placed only on the non-background color
	// the walls are removed as the ball leaves the area and new ones are made

	// thanks https://www.mazegenerator.net/ 
	// note: we made a vertical maze to start at the top and bottom 
	// then rotated the image to start at the sides
	var maze = new Pic("maze.png").addTo(holder).cache();
	// cache the image so we have a second canvas to use later
	// this allows us to get the color of the pixel under the ball
	// without getting the color of the ball ;-)

	new Label("START",25,null,white).pos(90,-20,LEFT,CENTER,holder);
	new Label("END",25,null,white).pos(60,-20,RIGHT,CENTER,holder);

	new Rectangle(W,H,new GradientColor([blue,green,orange,yellow])).addTo(holder).ble("darken");

	// create a Physics instance to handle making the ball bounce off walls
	// we will make walls dynamically only in the area of the ball
	// that way we don't make thousands of walls that we don't need
	// use the default outer walls and set gravity to 0
	var physics = new Physics(0);

	var ball = new Circle(8, purple).loc(130,262,holder).addPhysics(false, 2);
	// add an optional little finder
	new Circle(30,white.toAlpha(.3)).center(ball,0).wiggle("scale",1,.1,.2,.7,1.5);

	const end = new Rectangle(50,22,faint).pos(33,13,RIGHT,CENTER,holder)

	// create a Ticker to constantly apply a force to the ball
	// and make the walls near the ball
	// the factor is for the force
	// balance the speed with a tendency to go through walls if too fast

	const emitter = new Emitter({
		startPaused:true,
		obj:new Circle(ball.radius*1.3,clear,purple,3),
		animation:{scale:{min:.5, max:1.5}},
		force:{min:.5, max:1.5},
		gravity:0
	}).addTo(holder);

	const factor = .005; // force is incremental in time (make small)
	const max = .5; // limit the mouse distance (which limits force)
	Ticker.add(()=>{
		// make the walls
		makeWalls();

		// apply a force towards the mouse
		// do not use stage.mouseX and stage.mouseY
		// as they do not catch touch location
		// use any mouse event's mouseX and mouseY instead
		// we did that and stored the values in mouseX and mouseY
		let dX = constrain((F.mouseX-ball.x)*factor, -max, max);
		let dY = constrain((F.mouseY-ball.y)*factor, -max, max);
		ball.force(dX, dY);

		if (ball.hitTestBounds(end)) {
			emitter.loc(ball).spurt(20);
			ball.dynamic = false;
			ball.vis(false);
			ball.body.loc(150,262);
			win.animate({
				wait:.8,
				time:1.2,
				props:{scale:1},
				rewind:true,
				ease:"elasticOut"
			});
			timeout(3.5, ()=>{
				ball.dynamic = true;
				ball.vis(true);
				emitter.loc(ball).spurt(20);
			});
			
		}
	});

	
	// we want to find the color of the maze picture around where the ball is
	// we will put a wall at anywhere that is not the background color
	// so we access the context 2D of the cached picture
	var ctx = maze.cacheCanvas.getContext('2d');

	var num = 20; // test a 10x10 grid around the ball
	var space = 1; // the spacing of the points on the grid
	var radius = 1; // the radius of a wall placed at a point
	var walls = []; // an array to keep track of the active walls

	function makeWalls() {

		// remove any walls from the last time
		loop(walls, wall=>{
			physics.remove(wall);
		});
		walls = [];

		// loop through our grid
		loop(num, i=> {
			loop(num, j=> {

				// locate the x and y point on the grid for this i,j index
				const x = ball.x - num / 2 * space + i*space;
				const y = ball.y - num / 2 * space + j*space;

				// get the color data of the pixel at this grid location
				const data = ctx.getImageData(x, y, 1, 1).data;

				// Physics lets you automatically map physics bodies to ZIM objects
				// but in this case, we do not need visual objects
				// and we are creating many objects - so do not make the ZIM objects
				// Physics has methods to add only physics objects
				// so this is what we do in this case

				// make the wall if the color is darker than the background color
				if (data[0] < 150) {
					let wall = physics.makeCircle(radius, false);
					wall.loc(x,y);
					// add the wall to our array of walls
					walls.push(wall);
				}
			});
		});
	}


	// ~~~~~~~~~~~~~~~~~~~~~~~
    // THREEJS
   
    const three = new Three({
        width:window.innerWidth, 
        height:window.innerHeight, 
        cameraPosition:new THREE.Vector3(0,-.1,6.5),
        textureActive:true
    });

    const renderer = three.renderer;
    const scene = three.scene;
    const camera = three.camera;
    const canvas = three.canvas;
	
    const controls = new OrbitControls(camera, canvas);
	controls.rotateSpeed *= -.5; // seem to want to drag the maze - so reversed normal rotation
	controls.enableDamping = true;
	controls.dampingFactor = 0.1;
	three.preRender = ()=>{controls.update();}


    // TEXTUREACTIVES
    // more options - see https://zimjs.com/docs.html?item=TextureActives
    // only have ONE TextureActives object but there may be more than one TextureActive objects
    // use an array for the first parameter here if there is more than one TextureActive object
    // use the layer parameter so VR controls do not get in the way
	// physics needs to be first so it stays at 0,0 in ZIM.
    const textureActives = new TextureActives([wall, message, madeWith], THREE, three, renderer, scene, camera, controls);

    // To remove T key which toggles between 2D and 3D uncomment this:
    // textureActives.manager.toggleKey = -1;

	// if the object is a plane then we can use the makePanel of the ZIM Three helper
	const canvasWindow = three.makePanel({
        textureActive:message, 
        textureActives:textureActives, 
		scale:.03,
        curve:2 
    });
    scene.add(canvasWindow);  	
	canvasWindow.position.y = 5
	canvasWindow.position.z = -7

	// if the object is not a plane then we use the CanvasTexture
	// and this gets passed the canvas property of the ZIM TextureActive
	// note - the box will get the interactive texture on all sides which is okay
	// but here, we make the texture only on the front of the box
	const cylinderGeometry = new THREE.CylinderGeometry(10, 10, 10, 50);
	const cylinderTexture = new THREE.CanvasTexture(wall.canvas);
	const cylinderMaterial = [
		// backside of cylinder is flipped so flip back using ZIM three helper module method
		three.flipMaterial(THREE.MeshBasicMaterial, {map:cylinderTexture, side:THREE.BackSide}),
		new THREE.MeshBasicMaterial({transparent:true, opacity:0}),
		new THREE.MeshBasicMaterial({transparent:true, opacity:0})
	];
	const cylinder = new THREE.Mesh(cylinderGeometry, cylinderMaterial);           
	cylinder.rotation.y = 180*RAD;
	scene.add(cylinder);	
	textureActives.addMesh(cylinder);
	
	
	const zimLink = three.makePanel({
        textureActive:madeWith, 
        textureActives:textureActives, 
		scale:.015,
        curve:.5 
    });
    scene.add(zimLink);  	
	zimLink.position.y = -5
	zimLink.position.z = -9


	// THROTTLE TEST
    // on some older mobiles updating the cache constantly can bog at 60 fps
    let fps; const fpsT = Ticker.add(()=>{fps = Ticker.getFPS();});
    timeout(2, ()=>{if (fps < 50) {Ticker.setFPS(30);}; Ticker.remove(fpsT);});
		
}

// Docs for items used:
// https://zimjs.com/docs.html?item=Frame
// https://zimjs.com/docs.html?item=Pic
// https://zimjs.com/docs.html?item=Container
// https://zimjs.com/docs.html?item=Circle
// https://zimjs.com/docs.html?item=Rectangle
// https://zimjs.com/docs.html?item=Label
// https://zimjs.com/docs.html?item=Button
// https://zimjs.com/docs.html?item=tap
// https://zimjs.com/docs.html?item=noMouse
// https://zimjs.com/docs.html?item=addPhysics
// https://zimjs.com/docs.html?item=hitTestBounds
// https://zimjs.com/docs.html?item=animate
// https://zimjs.com/docs.html?item=wiggle
// https://zimjs.com/docs.html?item=loop
// https://zimjs.com/docs.html?item=pos
// https://zimjs.com/docs.html?item=loc
// https://zimjs.com/docs.html?item=ble
// https://zimjs.com/docs.html?item=reg
// https://zimjs.com/docs.html?item=sca
// https://zimjs.com/docs.html?item=addTo
// https://zimjs.com/docs.html?item=removeFrom
// https://zimjs.com/docs.html?item=center
// https://zimjs.com/docs.html?item=TextureActive
// https://zimjs.com/docs.html?item=TextureActives
// https://zimjs.com/docs.html?item=Emitter
// https://zimjs.com/docs.html?item=Physics
// https://zimjs.com/docs.html?item=timeout
// https://zimjs.com/docs.html?item=constrain
// https://zimjs.com/docs.html?item=GradientColor
// https://zimjs.com/docs.html?item=toAlpha
// https://zimjs.com/docs.html?item=STYLE
// https://zimjs.com/docs.html?item=Ticker
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.