Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URLs added here will be added as <link>s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Auto Save

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                
	<button id="test">Ignite</button><br>
	<canvas id="myCanvas"></canvas>
	<img id="tnt_top" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAv0lEQVQ4y5WTwQ1CMQxDu0IPSIgDV06IIdihY1ViFOZhA5YocqWHTGjh92ApbtI0ttL0uBza87rvULzKk4Jaa8d9d2qr/N2glNJu59yc53zsUKwz5TmDJ3URRFSoWAXiAnkuen1v4JoommlWbrMHvMb4Pt3UA0ADB5rxZugBmt1ATATRoxQ1xcsO5aNHXxIYbzTBXw/Yg9jEfXGfhh4wBXHU/XMPVAx3zeim4dQD3wPnjDziH4vkf2ErH+7BCn8BKYUBy/bJrtYAAAAASUVORK5CYII=" style="display:none" />
	<img id="tnt_side" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAl0lEQVQ4y2N45SzxH4Rv60v93yiq/p9UPgOIAeNM1BX8Typ/OBjwHwjOnTv3/+7Nm2AMYiPzcYmBMAiADQBxzM3CwFhaygbOh2lEFoOpAYmDDQBJ7Nu0Ga4QpgmmGMaGuQDERnYFiheQJWFsZIOQLQFZCjcA5k+YJDYvoHsH7gWQSZRgBlB0wKIEhEnlD4eUCPMTTIBUPgDuSGGtq/CnywAAAABJRU5ErkJggg==" style="display:none" />

  <img id="pumpkin_face" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA3ElEQVQ4y52TsQ3CQAxFT6JEogBaRmAEOjo2oGUHSmZgBArEJGzCICH/uHf68SUgUVixv+0X3zlJz/O6w27HeRfj+2k50BRTp2d6XTcdRoKYoqk4A3LD45BNwjbNsqEDlTZ4ScmlSuwFjafC/Sp9GgIUcO7pc/KTj0Qzxhtdl0/PXwCZ93wFMG7UgTcAncsL/LwNQBffAAqEZi6q6iVXn2NHYGQa8Vkl+Qbgq/JVTvmjgHjOX1YBPi4X5JuIm3G/ATCafweuXXaL4b8gAYh8/xMxj2N9BcRk1PBj/AbMk/q/pjGNTgAAAABJRU5ErkJggg==" style="display:none" />
  <img id="pumpkin_side" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAArUlEQVQ4y2M4ki/yH4YXhHH/R+evThRCEQPxYepANMPjLtn/MAyTgPFhinDxEQYs8wZjmI0wPkwRnA9VA7YEymaAmwgUgDkPXQOyJXBXQNkM+JwIdwESH1mMYgNAmIGQBqobgO5K2htQZ8v3H19awRuIID42A5D5DPg0EG0AzEnIGmBpH5eBMHkUA0CSyBkHpgEXH5yUQQIwQ5DZIBqE0TWgq4cbgCyJjNHl0fkA9tpNqcMeVOMAAAAASUVORK5CYII=" style="display:none" />
  <img id="pumpkin_top" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA40lEQVQ4y32TsRECMQwElRGQMaS0QRPkxFABXRBQBREBFEADdPKFGGT+PGtZEGgs+aXznaS312ldZI/jqp7TZTPcZbH7psCL3PxS/r9YQMZiWbu77b7+56wGZvKN9JTEV+WLMnOahJh83S8bqPzYn46B6Kp4ult5nq0ctosaC6RjN8uzph0avZggYkIWDYDIes2LBOJGSWx07UGHOn8gSJQiJn0P0Fm+5oUqjgxkJj0cHbvPk+MbphDHma23QHhv2YL8WuWWy1XOmqjVzTTHLR03EXsf565ecfSW6WVC/HmY5+cbLQxUQsZJLyIAAAAASUVORK5CYII=" style="display:none" />

	<img id="particles" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFAAAAAIAQMAAABUAup0AAAABlBMVEUAAAD///+l2Z/dAAAAAXRSTlMAQObYZgAAADtJREFUeF5jgAIBBhjg8IPQLCIiJiZgKQEBCSnp2SApDh4eHp2cHIgWGTk5uTiIFg4ODS1tBhgQAGoDAMD9BPjTWF5TAAAAAElFTkSuQmCC" style="display:none" />
	<img id="explosion_0" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAAC1JREFUWMPt0rENAEAIAzH2Xxr6r19ESPYCuSJVAHBVP6Lj6xEC4gHxEwLATwNEXH+BmjfSOgAAAABJRU5ErkJggg==" style="display:none" />
	<img id="explosion_1" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAADNJREFUWMPt07ENADAIBDH2X5r0aZAoQoTsBbjiiQCApryMHn8akQUB+wPGR/jFGwKwygHk+s8x5fbx0QAAAABJRU5ErkJggg==" style="display:none" />
	<img id="explosion_2" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAADRJREFUWMPt1LERACAIBEH6bxpzIwNHhNltgEueCAC6yk3Z4echpQF5SMDcACv44hEBcNsCkrY+0Cr8uGsAAAAASUVORK5CYII=" style="display:none" />
	<img id="explosion_3" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAADhJREFUWMPt1LERACAIBEH6bxpzIzVAhtltgEv4CAAe5ebb4dKQPDQzIC8JmBfgC1oMUYspBhhnAcbyvlCvRWcHAAAAAElFTkSuQmCC" style="display:none" />
	<img id="explosion_4" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAAD9JREFUWMPt1NEJACAMA9Hsv3ScQBRB08q9BXI/rQQAHXkiOv4kwpui41cifIgAAv4JiJ9hiUdU4hWvIgQAHQ2OzG2vAvel/wAAAABJRU5ErkJggg==" style="display:none" />
	<img id="explosion_5" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAAEBJREFUWMPt1bENACAMA8Hsv7SZgAKIsEF/C+QrpwoANmjCdvhKiBZZj7dHWAN0iAAC3g9gByKmOOIZRbxjAF8aY7U87oPV7roAAAAASUVORK5CYII=" style="display:none" />
	<img id="explosion_6" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAAEtJREFUWMPt1YEJACAIBVH3X/o3QVEomnJvgDwi0gxAR9ooG5wSokelw0Mj5BQekPoeTgem3IKCEEDArH+g1W84Zxl9sY5vQgxARwtTCOw+RqVRJwAAAABJRU5ErkJggg==" style="display:none" />
	<img id="explosion_7" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAE1JREFUOMtjYBgFeEF3S91/GCZZY2N1CRiTbAiyBphBMHZVacF/om1HN4hoV4BsQbYdnU+UAegYZhBRXoAZgm4Y2VFZUZz3H5keBZgAAAxPc/5mM/87AAAAAElFTkSuQmCC" style="display:none" />
	<img id="explosion_8" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAAGNJREFUWMPtlskJADAIBNN/06aCPKLjBTt/2UFEPUeIjdiDtuB0EXOCh3vEkPBoh3YK/BbjEq0CnkJ0IEcJEEO4XyCyiFCBKCX7H98DbQKkRPkZTrmIY36C8vARL9mYp1SIDC4JCpudwfBKAgAAAABJRU5ErkJggg==" style="display:none" />
	<img id="explosion_9" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAAG1JREFUWMPtltERACAIQtt/aZugrgSsTP7Rd50prZVKDtlAR5qGwMwKyiFWCskgdgrQITxGGgTydBSIvwHQ6YX3QwEg/5eyor2TS7sRHhP9SO0YJRfSRHoHQAERkoJkmeBYcxQiTyi9IpaX0qoDcKOLrfdg4n4AAAAASUVORK5CYII=" style="display:none" />
	<img id="explosion_10" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAAGNJREFUWMPtl9sNACAIA9l/aVzABwpViL1/6EFiiCKkIjrgb5kUIpRII8Et1BWINNQJFFgWIcNrCHgk1IC5wfXwXpPI8COBVaFuEjoNJDyFAEIC9qRg4VES8PP65O6n+pgQLw0xkkvtMf4oawAAAABJRU5ErkJggg==" style="display:none" />
	<img id="explosion_11" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAAGdJREFUWMPt1UkOACEQAkD//2nmbowr9HQU7kppXEpxDoMqhqSAGNECvInwLqQFhCIMmAYoVOgkJ4CJwCD5AEzEVnk9UFU+nHv3vmIy9IcDi6GuRlKuQsgOlKychQh5VkN/u9+Kr80HdRf4Mqi/QYIAAAAASUVORK5CYII=" style="display:none" />
	<img id="explosion_12" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAAGtJREFUWMPtltsNwDAIxLL/0u5vVLXNQwdEBS+AOQSitT8BUCK9RLgIHXkluFEphEgcN4acKfBACeQR4IUScHkg+OCs+C2MlrtXSmxFr5oNA8yeByaQ7yyLyDrZQR6nWXGliMtOu165sMJWXAcraMJAk/8hAAAAAElFTkSuQmCC" style="display:none" />
	<img id="explosion_13" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAAGJJREFUWMPt1ksOgCAQBFHuf+liw8IYPqOhMXG6LjBvNIKlOHEARtDKjeBSXoSfggF3wCcIA3ID6JQHwCBv/39AeLhCxCTpfxuLZNclgSTHJcG2b/MkybuUDt8BOfpdvx1aAVOJ6TMCQFXKAAAAAElFTkSuQmCC" style="display:none" />
	<img id="explosion_14" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAAEdJREFUWMPtlbENADAIw/j/6XTtUnUiIMV+wA4LVQCQiCRlB3CFNQGjEQRkB+iCANaPye0Bq+TWAOR2uR60Pgx9aFvVLnasPW5gfpAHbUXCAAAAAElFTkSuQmCC" style="display:none" />
	<img id="explosion_15" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAABGdBTUEAALGPC/xhBQAAADJJREFUWMPt0LERADAIAzH2X5qkTJ+COywt8JgqAIBkfTnA+rF47vtH14tnxfuxP/wTPVVha5UFxnNxAAAAAElFTkSuQmCC" style="display:none" />
	<img id="drawed"/><br>
              
            
!

CSS

              
                
		body {
			background-color: black;
		}
              
            
!

JS

              
                var test = false
var smoke_textures, explosion_textures, break_tex
var tnt_entity, shadow, breaks, tnt_ignite_smokes
var timeline
var surface_blocks = []
var tnt_block = []
var item_entities = []
var camera;

const isOld = true // pre 1.15 style

 const is_halloween = false

$(async function () {
	let wait = await imageReLoader(document.querySelector("#explosion_15"))

	const mag = 1//0.6
	const width = Math.floor(600 * mag);
	const height = Math.floor(400 * mag);
	const zoom = 1.4;

	const night = false;

	// initialize renderer -------------------------------------------------------------------------
	const renderer = new THREE.WebGLRenderer({
		canvas: document.querySelector('#myCanvas'),
		antialias: false,
		alpha: true,
		preserveDrawingBuffer: true
	});
	renderer.setClearColor(0x000000, 0);
	renderer.setPixelRatio(window.devicePixelRatio);
	renderer.setSize(width, height);

	// initilaize scene
	const scene = new THREE.Scene();

	// ====================================================================================================
	// explosion particles

	//---------smoke--------------
	smoke_textures = []
	smoke_textures.push(
		createSmoke(7),
		createSmoke(6),
		createSmoke(5),
		createSmoke(4),
		createSmoke(3),
		createSmoke(2),
		createSmoke(1),
		createSmoke(0),
		createSmoke(8),
	)
	// --------explosion----------
	explosion_textures = []
	explosion_textures.push(
		createExplosion(0),
		createExplosion(1),
		createExplosion(2),
		createExplosion(3),
		createExplosion(4),
		createExplosion(5),
		createExplosion(6),
		createExplosion(7),
		createExplosion(8),
		createExplosion(9),
		createExplosion(10),
		createExplosion(11),
		createExplosion(12),
		createExplosion(13),
		createExplosion(14),
		createExplosion(15),
		createExplosion(16),
	)
	// --------tnt break----------
	break_tex = CreateBreakTextures(is_halloween ? "#pumpkin_face": "#tnt_side");

	// ====================================================================================================
	let pos_x = 16 * 2, pos_y = -8, pos_z = 16 * 2;
	let field_zoom = 1.2;
	let ground_y = pos_y + field_zoom * 8;

	// TNT entity ---------------------

	tnt_entity = await new BBModelLoader.InitPromise({
		filename: is_halloween ? pumpkin_model: tnt_model,
		texture_name: is_halloween ? ["#pumpkin_side", "#pumpkin_face", "#pumpkin_top"] : ["#tnt_side", "#tnt_top", "#tnt_top"],
		side: THREE.FrontSide,
	}).loadBlock();

	tnt_entity.object.scale.set(field_zoom, field_zoom, field_zoom)
	tnt_entity.object.position.set(pos_x, ground_y, pos_z);
	tnt_entity.object.visible = false

	let white = new THREE.MeshBasicMaterial({ color: "white" })
	tnt_entity.whiteMaterials = []
	tnt_entity.whiteMaterials.push(
		white, white.clone(), white.clone()
	)

	scene.add(tnt_entity.object)

	// TNT entity shadow -------
	let shadow_tex = new THREE.TextureLoader().load("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAYFBMVEUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD6T+iNAAAAH3RSTlMACBAYICgwOEBIUFhgaHB4gIePl5+vt7/Hz9ff5+/3vuRRMQAAAVNJREFUeF7F1dvOqjAQBeDVFhGVk/6gG4Gu93/LHTVNMEih0xi/+84x6eCrkuxpJ3qbNx0d3tsyRQBddJzpq2Rr8oYL2hTrTEOP2+o8ipFe9qzgoa9c1XmKyAZuYE9YcLDcpsJHR8utak9+cYTMMkQ+W//AIHaPdzcG6jWmSgb7w4SxDDdtoqVABwcpRY6zAqQlGAod8FJRqMHLnUKjciOM66Gk2GW6A/keBsopAIoRUjdDqROAjBFqAPnPAxQ/r+AQG2DPCAUATUf6L46U0+4kCPV4qMm4LymjWI4HNVLK4OlCoavwrsxvfEeRQeEFR4pUQFQJ1sCRbbLERCM9rY7uGcju4MiaKODITnSLmT8GuCk4okF2GoiJ8M+9F86hVVhyGLnKlvAwV67oUvidBnrYCqtUuRhiPBtsofL24+wLjc103vScGNsyQSiTVfVLluB7/gM/qWGG/DVp0QAAAABJRU5ErkJggg==")
	shadow_tex.magFilter = THREE.NearestFilter
	shadow_tex.minFilter = THREE.NearestFilter
	shadow_tex.type = THREE.FloatType
	shadow = new THREE.Mesh(
		new THREE.PlaneGeometry(10, 10),
		new THREE.MeshLambertMaterial({
			map: shadow_tex,
			transparent: true,
			opacity: 0.5,
		})
	)
	shadow.scale.set(field_zoom, field_zoom, field_zoom)
	shadow.rotation.x = deg2rad(-90)
	shadow.position.set(pos_x, pos_y + 0.01, pos_z)
	shadow.visible = false
	scene.add(shadow)

	// TNT ignite smokes initialize ----------
	let smoke_option = function (pos) {
		return {
			color: Math.floor(Math.random() * 6) * 0x10,
			type: 0,
			x: pos.x,
			y: pos.y,
			z: pos.z,
			dir: Math.random() * 359,
			str: 0.4 + Math.random() * 0.5,
			rise: (24 + Math.random() * 5),
			scale: (4 + Math.random() * 1),
		}
	};

	tnt_ignite_smokes = []
	for (let i = 0; i < 12; i++) {
		let time = (i / 12) * 1.5

		let pos = {
			x: pos_x + (time < 0.2 ? 1 * (time % 0.2) : 1),
			y: ground_y + 4 + (time < 0.2 ? Math.sin((time % 0.2) * Math.PI) * 8 : 0),
			z: pos_z + (time < 0.2 ? -1.5 * (time % 0.2) : -1.5),
		}

		let smoke = new Smoke(smoke_option(pos));
		smoke.start_time = time;
		tnt_ignite_smokes.push(smoke);
		scene.add(smoke.sprite);
	}

	// TNT block break particles --------------

	breaks = [];
	for (let i = 0; i < 64; i++) {
		let angle = Math.random() * 360
		let position = new THREE.Vector3(
			(((i % 4)) * 4 - 8) * field_zoom,
			(Math.floor(i / 16) * 4 - 8) * field_zoom,
			(Math.floor((i % 16) / 4) * 4 - 8) * field_zoom,
		)
		position.add(new THREE.Vector3(
			pos_x,
			pos_y + 8,
			pos_z
		))

		let debri = new Debris({
			position: position,
			tex: Math.random() * 8,
			angle: Math.random() * 360,
			fall_bias: 200,
			begin_height: -Math.random() * 8,
			spread: 16 + Math.random() * 16 * field_zoom,
			begin_pos: 0.2 + Math.random() * 0.1,
			curve_height: 4 + Math.random() * 4,
			scale: Math.random() * 2 + 2,
			maxfall: pos_y,
		});

		scene.add(debri.sprite);

		breaks.push(debri);
	}

	// Fireworks --------------------
	let fireworks = [];
	let blacksmokes = [];

	if (isOld) {
		// white explosion smoke -----------------
		for (let i = 0; i < 800; i++) {
			let h_out = sigmoid(Math.random() * 2) * 200 * 1.38
			let angle = Math.random() * 360

			let vec = new THREE.Vector3(
				h_out * Math.sin(deg2rad(angle)),
				h_out * Math.cos(deg2rad(angle)),
				0,
			)

			axis = new THREE.Vector3(0, 1, 0)
			vec.applyAxisAngle(axis, deg2rad(Math.random() * 360))

			axis = new THREE.Vector3(0, 0, 1)
			vec.applyAxisAngle(axis, deg2rad(Math.random() * 360))

			vec.y *= 0.8

			let origin = new THREE.Vector3(
				pos_x * field_zoom,
				ground_y,
				pos_z * field_zoom
			)

			let firework = new Firework({
				pos: vec,
				origin: origin,
				color: Math.floor(Math.random() * 0x40 + 0xb0),
				scale: 2 + Math.random() * 14,
				durability: Math.random() * 0.6,
				down: 16,
				limit: pos_y,
			});
			firework.sprite.visible = false
			fireworks.push(firework);
			scene.add(firework.sprite);
		}

		// black explosion smoke ---------------------
		for (let i = 0; i < 800; i++) {
			let h_out = 100 + sigmoid(Math.random() * 2) * 130 * 1.5
			let angle = Math.random() * 360

			let vec = new THREE.Vector3(
				h_out * Math.sin(deg2rad(angle)),
				h_out * Math.cos(deg2rad(angle)),
				0,
			)

			axis = new THREE.Vector3(0, 1, 0)
			vec.applyAxisAngle(axis, deg2rad(Math.random() * 360))

			axis = new THREE.Vector3(0, 0, 1)
			vec.applyAxisAngle(axis, deg2rad(Math.random() * 360))
			
			vec.y *= 0.8

			let origin = new THREE.Vector3(
				pos_x * field_zoom,
				ground_y,
				pos_z * field_zoom
			)

			let blacksmoke = new BlackSmoke({
				pos: vec,
				origin: origin,
				color: Math.floor(Math.random() * 0x40),
				scale: 4 + Math.random() * 2,
				durability: Math.random() * 0.4,
				down: 1,
				limit: pos_y,
			});
			blacksmoke.sprite.visible = false
			blacksmokes.push(blacksmoke);
			scene.add(blacksmoke.sprite);
		}
	}

	// Round shaped smoke particles -------------------
	let bomb = [];
	let bomb_count = (isOld ? 32: 64);
	for (let i = 0; i < bomb_count; i++) {
		let h_out = 30 + sigmoid(Math.random() * 2) * 80
		let angle = Math.random() * 360

		h_out *= isOld ? 1: 1.2;

		let vec = new THREE.Vector3(
			h_out * Math.sin(deg2rad(angle)),
			h_out * Math.cos(deg2rad(angle)),
			0,
		)

		axis = new THREE.Vector3(0, 1, 0)
		vec.applyAxisAngle(axis, deg2rad(Math.random() * 360))

		axis = new THREE.Vector3(0, 0, 1)
		vec.applyAxisAngle(axis, deg2rad(Math.random() * 360))
			
		vec.y *= 0.8

		vec.add(new THREE.Vector3(
			pos_x * field_zoom,
			ground_y,
			pos_z * field_zoom
		))

		let explode = new Explode({
			pos: vec,
			scale: Math.random() * 40 + 20,
			color: Math.floor(Math.random() * 0x60 + 0xA0),
		})

		bomb.push({
			sprite: explode,
			start: (i / bomb_count) * (isOld ? 0.08: 0.1),
			interval: (isOld ? 0.06: 0.08) + Math.random() * 0.06,
		})

		scene.add(explode.sprite)

	}
  
  if(is_halloween){		// creeper shape
		let h_out = 140;
		let cell = h_out / 6
		let cords = []

    let origin = new THREE.Vector3(
      pos_x * field_zoom,
      ground_y + 2 * 16 * field_zoom,
      pos_z * field_zoom
    )

		// mouth up-right
		cords.push(new THREE.Vector3(0, 1, 0))
		cords.push(new THREE.Vector3(1, 1, 0))
		// right eye
		cords.push(new THREE.Vector3(1, 3, 0))
		cords.push(new THREE.Vector3(3, 3, 0))
		cords.push(new THREE.Vector3(3, 1, 0))
		cords.push(new THREE.Vector3(1, 1, 0))
		// mouth bottom-right
		cords.push(new THREE.Vector3(1, 0, 0))
		cords.push(new THREE.Vector3(2, 0, 0))
		cords.push(new THREE.Vector3(2, -3, 0))
		cords.push(new THREE.Vector3(1, -3, 0))
    cords.push(new THREE.Vector3(1, -2, 0))
		// mouth bottom-center
		cords.push(new THREE.Vector3(0, -2, 0))
		// mouth bottom-left
		cords.push(new THREE.Vector3(-1, -2, 0))
		cords.push(new THREE.Vector3(-1, -3, 0))
		cords.push(new THREE.Vector3(-2, -3, 0))
		cords.push(new THREE.Vector3(-2, 0, 0))
		cords.push(new THREE.Vector3(-1, 0, 0))
		cords.push(new THREE.Vector3(-1, 1, 0))
		// left eye
		cords.push(new THREE.Vector3(-1, 3, 0))
		cords.push(new THREE.Vector3(-3, 3, 0))
		cords.push(new THREE.Vector3(-3, 1, 0))
		cords.push(new THREE.Vector3(-1, 1, 0))
		cords.push(new THREE.Vector3(0, 1, 0))
		
		let len = cords.length
		for(let i=0; i<len-1; i++){
			let vec1 = cords[i]
			let vec2 = cords[i+1]

			for(let j=1; j<4; j++){
				let dist = (vec1.distanceTo(vec2) / 4) * j
				let angle = Math.atan2(vec2.y - vec1.y, vec2.x - vec1.x)
				
				cords.push(
					new THREE.Vector3(
						vec1.x + (Math.cos(angle) * dist),
						vec1.y + (Math.sin(angle) * dist),
						0,
					)
				)
			}
		}
		
		for(let i=0; i<cords.length; i++){
      
      let option = {
				origin: origin,
				color: Math.floor(Math.random() * 0x40),
				scale: 8,
				durability: Math.random() * 0.2,
				down: 10,
				limit: pos_y,
			};
      
      option.pos = cords[i].clone().multiplyScalar(cell);
      
			let firework = new Firework(option);
			firework.sprite.visible = false
			fireworks.push(firework);
			scene.add(firework.sprite);

			let axis = new THREE.Vector3(0, 1, 0)
      option.pos = cords[i].clone().multiplyScalar(cell).applyAxisAngle(axis, deg2rad(5));
			firework = new Firework(option);
			firework.sprite.visible = false
			fireworks.push(firework);
			scene.add(firework.sprite);
			
			option.pos = cords[i].clone().multiplyScalar(cell).applyAxisAngle(axis, deg2rad(-5));
			firework = new Firework(option);
			firework.sprite.visible = false
			fireworks.push(firework);
			scene.add(firework.sprite);
		}
  }

	// Field data ------------------------------------------------------------

	let field_wide = 32;

	let airs = [
		{ x: 3, y: -1, z: 0 },
		{ x: 4, y: -1, z: 0 },
		{ x: 5, y: -1, z: 0 },
		{ x: 6, y: -1, z: 0 },

		{ x: 1, y: -1, z: 1 },
		{ x: 2, y: -1, z: 1 },
		{ x: 3, y: -1, z: 1 },
		{ x: 4, y: -2, z: 1 },
		{ x: 5, y: -2, z: 1 },
		{ x: 6, y: -2, z: 1 },
		{ x: 7, y: -1, z: 1 },

		{ x: 0, y: -1, z: 2 },
		{ x: 1, y: -1, z: 2 },
		{ x: 2, y: -2, z: 2 },
		{ x: 3, y: -3, z: 2 },
		{ x: 4, y: -2, z: 2 },
		{ x: 5, y: -2, z: 2 },
		{ x: 6, y: -2, z: 2 },
		{ x: 7, y: -1, z: 2 },

		{ x: 0, y: -1, z: 3 },
		{ x: 1, y: -1, z: 3 },
		{ x: 2, y: -3, z: 3 },
		{ x: 3, y: -3, z: 3 },
		{ x: 4, y: -3, z: 3 },
		{ x: 5, y: -3, z: 3 },
		{ x: 6, y: -3, z: 3 },
		{ x: 7, y: -3, z: 3 },
		{ x: 8, y: -1, z: 3 },

		{ x: 0, y: -1, z: 4 },
		{ x: 1, y: -2, z: 4 },
		{ x: 2, y: -3, z: 4 },
		{ x: 3, y: -3, z: 4 },
		{ x: 4, y: -3, z: 4 },
		{ x: 5, y: -3, z: 4 },
		{ x: 6, y: -3, z: 4 },
		{ x: 7, y: -2, z: 4 },
		{ x: 8, y: -1, z: 4 },

		{ x: 1, y: -2, z: 5 },
		{ x: 2, y: -2, z: 5 },
		{ x: 3, y: -2, z: 5 },
		{ x: 4, y: -3, z: 5 },
		{ x: 5, y: -3, z: 5 },
		{ x: 6, y: -3, z: 5 },
		{ x: 7, y: -2, z: 5 },
		{ x: 8, y: -2, z: 5 },

		{ x: 1, y: -1, z: 6 },
		{ x: 2, y: -2, z: 6 },
		{ x: 3, y: -3, z: 6 },
		{ x: 4, y: -3, z: 6 },
		{ x: 5, y: -3, z: 6 },
		{ x: 6, y: -3, z: 6 },
		{ x: 7, y: -2, z: 6 },
		{ x: 8, y: -2, z: 6 },

		{ x: 1, y: -1, z: 7 },
		{ x: 2, y: -2, z: 7 },
		{ x: 3, y: -2, z: 7 },
		{ x: 4, y: -2, z: 7 },
		{ x: 5, y: -2, z: 7 },
		{ x: 6, y: -1, z: 7 },
		{ x: 7, y: -1, z: 7 },

		{ x: 2, y: -1, z: 8 },
		{ x: 4, y: -1, z: 8 },
		{ x: 5, y: -1, z: 8 },
		{ x: 6, y: -1, z: 8 },
		{ x: 7, y: -1, z: 8 },
	]

	airs.forEach(air => {
		air.x += 12;
		air.z += 12;
	})

	// for coordinate check
	let check = function (x, z) {
		for (let i = 0; i < airs.length; i++) {
			let air = airs[i]
			if (air.x == x && air.z == z) {
				return air.y
			}
		}
		return 0
	}

	// for coordinate check
	let check_all = function (x, y, z) {
		let ret = {
			n: check(x, z - 1) > y,
			ne: check(x - 1, z - 1) > y,
			nw: check(x + 1, z - 1) > y,
			s: check(x, z + 1) > y,
			se: check(x - 1, z + 1) > y,
			sw: check(x + 1, z + 1) > y,
			e: check(x - 1, z) > y,
			w: check(x + 1, z) > y
		}
		ret.all = (ret.n && ret.s && ret.e && ret.w)
		ret.or = (ret.n || ret.ne || ret.nw || ret.s || ret.se || ret.sw || ret.e || ret.w)
		return ret
	}

	// loading textures for field
	let texture = new THREE.TextureLoader().load("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAulBMVEVAUydWcDVKYS5NZC9NZS9OZjBRajJTbDNUbTNJYC1gfTtIXixLYi5JXy1PZzFQaDFQaTFRaTJKYC1SazJSazNHXSxIXSxUbjRVbjRMYy5XcTVYczZadTdffDpifzxigDxmhT5hfjtDVylvkURWcTVbdzhtjkNGWytcdzhdeTldejleejpeezpHXCtEWSpVbzRhfzxZdDZZdDdjgT1MYy9nh0BqikFadjdFWipoiEBEWCpceDlnhj9FWisoFnW1AAAA3klEQVR4XgXBA2JFQRAAsJk1nm1+2qjuf60mkM/q8RDWb86c8hYCKPwlVm4VWXNQs0892Aapw1kc52/T02/3CVPJMalSaUmAwmtT+CE96e6se01ue8lYANXidNDnoqYFIycW5pBd97Ek3Dr0t0taFCDozYuPOwd7qsXSGmjr59jp/jqMkSxVkwFGX4G/GF2aSBMOQkPd4D56hmr1aYlgKMDpH1t0JEfprwebcnih5lVS/vGEp+/9B0GbSaaVnjfK1Etm7rDdrNbDJVdZUVBz8z5QnhnfLXIVro7GiVnzD2McFfc1sfzxAAAAAElFTkSuQmCC")
	texture.magFilter = THREE.NearestFilter
	texture.minFilter = THREE.NearestFilter
	texture.type = THREE.FloatType

	let texture2 = new THREE.TextureLoader().load("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA0UlEQVR42nWSsQ1CMQxEvQ4rINFR0SOBxAgpaRgA/Q3YhelAF+mi9+/nF1YS2zn7zq7v+/H7tEu31/XYbea7nw6ruM/SRYlM0HtZluEzIO+KycporsI3qzGHeSW01tpwsJukkl0MAAcV0GdyJ6AKpa/cVlZyq9SG9yFicpR4TKDipOmzMjnH525m/g6QTp5uOT8zNt2D5J68SaVyPORsYPtJxVrVjBcraHSkwDH3TUxnguyZu1pRkD1v541wpjHb0MokKkz+WWgDkDtgkfbm7wJ/9oUoq3qXqFkAAAAASUVORK5CYII=")
	texture2.magFilter = THREE.NearestFilter
	texture2.minFilter = THREE.NearestFilter
	texture2.type = THREE.FloatType

	let texture3 = new THREE.TextureLoader().load("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAARVBMVEVEWCpIXixJYC1KYC1LYi5PZzFQaDFQaTFSazJSazNUbTNVbzRZPSladjdgfTtigDxlhD5sbGx0WER5VTqHh4eWbEq5hVyKVdATAAAAhklEQVQYGQWACVLDQBADh8sYVl4cOq3/PzU1+1zHPvda177u92vNPvZ5Z3/ca//uY3/Nyv25c2Zf6y+zMnkkP0ne8h0SRmyEJA8MQxSTQIBOgSe0tIgDrYJR0BFEggJhMJAA0OIgLTVt9MkIWnwokQwRLQDQDsI/ShqqIykhLYKOVHmioOQFPXERD69I42oAAAAASUVORK5CYII=")
	texture3.magFilter = THREE.NearestFilter
	texture3.minFilter = THREE.NearestFilter
	texture3.type = THREE.FloatType

	let texture4 = new THREE.TextureLoader().load("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAvklEQVQ4T4WS2w3FIAxDMwsrdBgGYCjGbeVKRgeL3vsRJQ3Bj5QaY9zXdW3Re7/nnLfOXCvz3LlUaFDhS8w6V3avtbaBvABuZu3LvuCwIgEW5VqyQDhI+Wm1zGimHHBtSyRbFiiP37R16r0ARLWvfz2rFUh9sZ/YaMvgRfT811/sjDoxUQ3ZuGADFh9RbpugZOXrLW44LaQ9kinWEikt5VJdxrZEZzWtisPcD1WsHaT8X1Y2BUR3ncsySL4H9R5CYf5qPXwY2QAAAABJRU5ErkJggg==")
	texture4.magFilter = THREE.NearestFilter
	texture4.minFilter = THREE.NearestFilter
	texture4.type = THREE.FloatType

	let material = new THREE.MeshLambertMaterial({
		map: texture,
		transparent: false,
		alphaTest: 0.999,
	})

	// A TNT Block -------------------------------
	center_rotations = []
	{
		let tnt_side = new THREE.TextureLoader().load($(is_halloween ?"#pumpkin_side": "#tnt_side")[0].src)
		tnt_side.magFilter = THREE.NearestFilter
		tnt_side.minFilter = THREE.NearestFilter
		let tnt_face = new THREE.TextureLoader().load($(is_halloween ?"#pumpkin_face": "#tnt_side")[0].src)
		tnt_side.magFilter = THREE.NearestFilter
		tnt_side.minFilter = THREE.NearestFilter
		let tnt_top = new THREE.TextureLoader().load($(is_halloween ?"#pumpkin_top" :"#tnt_top")[0].src)
		tnt_top.magFilter = THREE.NearestFilter
		tnt_top.minFilter = THREE.NearestFilter

		let top = new THREE.Mesh(
			new THREE.PlaneGeometry(16, 16),
			new THREE.MeshLambertMaterial({
				map: tnt_top,
				transparent: false,
				alphaTest: 0.999,
			})
		);
		top.scale.set(field_zoom, field_zoom, field_zoom)
		top.position.set(pos_x, pos_y + 16 * field_zoom, pos_z)
		top.rotation.x = deg2rad(-90)
		scene.add(top)
		tnt_block.push(top)

		let side_north = top.clone()
		side_north.material = new THREE.MeshLambertMaterial({
			map: tnt_side,
			transparent: false,
			alphaTest: 0.999,
			side: THREE.DoubleSide
		})
		addVertexUVs(side_north);
		side_north.material.aoMap = createAOMap([0, 0, 0, 1, 1, 1, 0, 0])
		side_north.position.set(pos_x, pos_y + 8 * field_zoom, pos_z - 8 * field_zoom)
		side_north.rotation.x = 0
		side_north.rotation.y = deg2rad(180)
		scene.add(side_north)
		tnt_block.push(side_north)

		let side_east = side_north.clone()
		side_east.position.set(pos_x - 8 * field_zoom, pos_y + 8 * field_zoom, pos_z)
		side_east.rotation.x = deg2rad(180)
		side_east.rotation.y = deg2rad(-90)
		scene.add(side_east)
		tnt_block.push(side_east)

		let side_south = side_north.clone()
		side_south.position.set(pos_x, pos_y + 8 * field_zoom, pos_z + 8 * field_zoom)
		side_south.rotation.x = 0
		side_south.rotation.z = deg2rad(0)
    if(is_halloween){
      side_south.material = new THREE.MeshLambertMaterial({
        map: tnt_face,
        transparent: false,
        alphaTest: 0.999,
        side: THREE.DoubleSide
      })
    }
		scene.add(side_south)
		tnt_block.push(side_south)

		let side_west = side_north.clone()
		side_west.position.set(pos_x + 8 * field_zoom, pos_y + 8 * field_zoom, pos_z)
		side_west.rotation.x = deg2rad(-180)
		side_west.rotation.y = deg2rad(90)
		scene.add(side_west)
		tnt_block.push(side_west)

		// Grass surfaces of around of the TNT for AO map

		let mate = new THREE.MeshLambertMaterial({
			map: texture,
			transparent: false,
			alphaTest: 0.999,
		})

		let grasses = []
		for (let i = 0; i < 9; i++) {
			if (i == 4) {
				grasses.push({});
				continue;
			}
			copy = top.clone()
			copy.geometry = new THREE.PlaneGeometry(16, 16);
			copy.position.y -= 16 * field_zoom
			copy.material = mate.clone()
			addVertexUVs(copy);
			scene.add(copy)
			grasses.push(copy);
			tnt_block.push(copy);
		}

		grasses[0].material.aoMap = createAOMap([0, 0, 0, 0, 0, 0, 0, 1])
		grasses[1].material.aoMap = createAOMap([1, 0, 0, 0, 0, 0, 0, 0])
		grasses[2].material.aoMap = createAOMap([0, 1, 0, 0, 0, 0, 0, 0])
		grasses[3].material.aoMap = createAOMap([0, 0, 0, 0, 0, 0, 1, 0])
		grasses[5].material.aoMap = createAOMap([0, 0, 1, 0, 0, 0, 0, 0])
		grasses[6].material.aoMap = createAOMap([0, 0, 0, 0, 0, 1, 0, 0])
		grasses[7].material.aoMap = createAOMap([0, 0, 0, 0, 1, 0, 0, 0])
		grasses[8].material.aoMap = createAOMap([0, 0, 0, 1, 0, 0, 0, 0])

		grasses[0].position.add(new THREE.Vector3(+16 * field_zoom, 0, +16 * field_zoom))
		grasses[1].position.add(new THREE.Vector3(0 * field_zoom, 0, +16 * field_zoom))
		grasses[2].position.add(new THREE.Vector3(-16 * field_zoom, 0, +16 * field_zoom))
		grasses[3].position.add(new THREE.Vector3(+16 * field_zoom, 0, 0))
		grasses[5].position.add(new THREE.Vector3(-16 * field_zoom, 0, 0))
		grasses[6].position.add(new THREE.Vector3(+16 * field_zoom, 0, -16 * field_zoom))
		grasses[7].position.add(new THREE.Vector3(0 * field_zoom, 0, -16 * field_zoom))
		grasses[8].position.add(new THREE.Vector3(-16 * field_zoom, 0, -16 * field_zoom))

		for (let i = 0; i < 9; i++) {
			let r = Math.floor(Math.random() * 4);
			center_rotations.push(r);
			if (i == 4) continue;
			faceRotate(grasses[i], 0, 0, r);
			faceRotate(grasses[i], 0, 1, r);
		}

	}

	// Create Field ---------------------------------------------------------

	let center_grass_pos = [15, 16, 17]

	for (let i = 0; i < field_wide * field_wide; i++) {
		let x = Math.floor(i % field_wide)
		let z = Math.floor(i / field_wide)

		let y = check(x, z)

		let surface_grass = new THREE.Mesh(
			new THREE.PlaneGeometry(16, 16),
			material
		)
		if (x == 16 && z == 16) {
			surface_grass.material = new THREE.MeshLambertMaterial({
				map: texture2,
				transparent: false,
				alphaTest: 0.999,
			})
		}
		surface_grass.scale.set(field_zoom, field_zoom, field_zoom)
		surface_grass.rotation.x = deg2rad(-90)
		surface_grass.position.set(pos_x + (x - field_wide / 2) * 16 * field_zoom, pos_y, pos_z + (z - field_wide / 2) * 16 * field_zoom)
		if (center_grass_pos.indexOf(x) >= 0 && center_grass_pos.indexOf(z) >= 0) {
			let r = center_rotations.pop()
			faceRotate(surface_grass, 0, 0, r);
			faceRotate(surface_grass, 0, 1, r);
		} else {
			let r = Math.floor(Math.random() * 4)
			faceRotate(surface_grass, 0, 0, r);
			faceRotate(surface_grass, 0, 1, r);
		}
		scene.add(surface_grass)

		if (y != 0) {
			surface_blocks.push(surface_grass);

			let ao = check_all(x, y, z)
			let grass = new THREE.Mesh(
				new THREE.PlaneGeometry(16, 16),
				new THREE.MeshLambertMaterial({
					map: y > -3 ? texture2 : texture4,
					transparent: false,
					alphaTest: 0.999,
				})
			)
			addVertexUVs(grass);

			if (y == -3) {
				let r = Math.floor(Math.random() * 2)
				if (r == 1) {
					faceFlip(grass, 0, 0);
					faceFlip(grass, 0, 1);
				}
				r = Math.floor(Math.random() * 2)
				if (r == 1){
					faceVFlip(grass, 0, 0);
					faceVFlip(grass, 0, 1);
				}
			} else {
				let r = Math.floor(Math.random() * 4)
				faceRotate(grass, 0, 0, r);
				faceRotate(grass, 0, 1, r);
				r = Math.floor(Math.random() * 2)
				if (r == 1) {
					faceFlip(grass, 0, 0);
					faceFlip(grass, 0, 1);
				}
				r = Math.floor(Math.random() * 2)
				if (r == 1){
					faceVFlip(grass, 0, 0);
					faceVFlip(grass, 0, 1);
				}
			}
			grass.scale.set(field_zoom, field_zoom, field_zoom)
			grass.rotation.x = deg2rad(-90)

			if (ao.or) {
				grass.material.aoMap = createAOMap([
					check(x, z - 1) > y ? 1 : 0,
					check(x + 1, z - 1) > y ? 1 : 0,
					check(x + 1, z) > y ? 1 : 0,
					check(x + 1, z + 1) > y ? 1 : 0,
					check(x, z + 1) > y ? 1 : 0,
					check(x - 1, z + 1) > y ? 1 : 0,
					check(x - 1, z) > y ? 1 : 0,
					check(x - 1, z - 1) > y ? 1 : 0,
				])
			}
			grass.position.set(pos_x + (x - field_wide / 2) * 16 * field_zoom, pos_y + y * 16 * field_zoom, pos_z + (z - field_wide / 2) * 16 * field_zoom)
			scene.add(grass)

			for (let j = -1; j >= y; j--) {

				let yy = j
				let tex = yy < -1 ? texture2 : texture3
				let mate = new THREE.MeshLambertMaterial({
					map: tex,
					transparent: false,
					alphaTest: 0.999,
					side: THREE.DoubleSide,
				})

				// north
				if (check(x, z - 1) > yy) {
					mesh = new THREE.Mesh(
						new THREE.PlaneGeometry(16, 16),
						mate.clone()
					)
					mesh.scale.set(field_zoom, field_zoom, field_zoom)
					addVertexUVs(mesh);

					mesh.material.aoMap = createAOMap([
						0,
						check(x + 1, z) > yy + 1 ? 1 : 0,
						check(x + 1, z) > yy ? 1 : 0,
						check(x + 1, z) > yy - 1 ? 1 : 0,
						check(x, z) > yy - 1 ? 1 : 0,
						check(x - 1, z) > yy - 1 ? 1 : 0,
						check(x - 1, z) > yy ? 1 : 0,
						check(x - 1, z) > yy + 1 ? 1 : 0,
					])

					mesh.position.set(pos_x + (x - field_wide / 2) * 16 * field_zoom, pos_y + yy * 16 * field_zoom + 8 * field_zoom, pos_z + (z - field_wide / 2) * 16 * field_zoom - 8 * field_zoom)
					mesh.rotation.x = 0
					scene.add(mesh)
				}
				// south
				if (check(x, z + 1) > yy) {
					mesh = new THREE.Mesh(
						new THREE.PlaneGeometry(16, 16),
						mate.clone()
					)
					mesh.scale.set(field_zoom, field_zoom, field_zoom)
					addVertexUVs(mesh);
					mesh.material.aoMap = createAOMap([
						0,
						check(x + 1, z) > yy + 1 ? 1 : 0,
						check(x + 1, z) > yy ? 1 : 0,
						check(x + 1, z) > yy - 1 ? 1 : 0,
						check(x, z) > yy - 1 ? 1 : 0,
						check(x - 1, z) > yy - 1 ? 1 : 0,
						check(x - 1, z) > yy ? 1 : 0,
						check(x - 1, z) > yy + 1 ? 1 : 0,
					])
					mesh.position.set(pos_x + (x - field_wide / 2) * 16 * field_zoom, pos_y + yy * 16 * field_zoom + 8 * field_zoom, pos_z + (z - field_wide / 2) * 16 * field_zoom + 8 * field_zoom)
					mesh.rotation.x = 0
					scene.add(mesh)
				}
				// east
				if (check(x - 1, z) > yy) {
					mesh = new THREE.Mesh(
						new THREE.PlaneGeometry(16, 16),
						mate.clone()
					)
					mesh.scale.set(field_zoom, field_zoom, field_zoom)
					addVertexUVs(mesh);
					mesh.material.aoMap = createAOMap([
						0,
						check(x, z - 1) > yy + 1 ? 1 : 0,
						check(x, z - 1) > yy ? 1 : 0,
						check(x, z - 1) > yy - 1 ? 1 : 0,
						check(x, z) > yy - 1 ? 1 : 0,
						check(x, z + 1) > yy - 1 ? 1 : 0,
						check(x, z + 1) > yy ? 1 : 0,
						check(x, z + 1) > yy + 1 ? 1 : 0,
					])
					mesh.position.set(pos_x + (x - field_wide / 2) * 16 * field_zoom - 8 * field_zoom, pos_y + yy * 16 * field_zoom + 8 * field_zoom, pos_z + (z - field_wide / 2) * 16 * field_zoom)
					mesh.rotation.x = 0
					mesh.rotation.y = deg2rad(90)
					scene.add(mesh)
				}
				// west
				if (check(x + 1, z) > yy) {
					mesh = new THREE.Mesh(
						new THREE.PlaneGeometry(16, 16),
						mate.clone()
					)
					mesh.scale.set(field_zoom, field_zoom, field_zoom)
					addVertexUVs(mesh);
					mesh.material.aoMap = createAOMap([
						0,
						check(x, z - 1) > yy + 1 ? 1 : 0,
						check(x, z - 1) > yy ? 1 : 0,
						check(x, z - 1) > yy - 1 ? 1 : 0,
						check(x, z) > yy - 1 ? 1 : 0,
						check(x, z + 1) > yy - 1 ? 1 : 0,
						check(x, z + 1) > yy ? 1 : 0,
						check(x, z + 1) > yy + 1 ? 1 : 0,
					])
					mesh.position.set(pos_x + (x - field_wide / 2) * 16 * field_zoom + 8 * field_zoom, pos_y + yy * 16 * field_zoom + 8 * field_zoom, pos_z + (z - field_wide / 2) * 16 * field_zoom)
					mesh.rotation.x = 0
					mesh.rotation.y = deg2rad(90)
					scene.add(mesh)
				}
			}
		}
	}

	// Item entities ----------------------
	for(let i=0; i<airs.length; i++){
		for(let j=0; j<-airs[i].y; j++){
			if(Math.random() < 0.2){
				let cube = new THREE.Mesh(
					new THREE.BoxGeometry(16, 16, 16),
					new THREE.MeshLambertMaterial({
						map: texture2,
						alphaTest: 0.9,
					})
				)
				cube.scale.set(field_zoom * 0.25, field_zoom * 0.25, field_zoom * 0.25)
				let shadow = new THREE.Mesh(
					new THREE.PlaneGeometry(10, 10),
					new THREE.MeshLambertMaterial({
						map: shadow_tex,
						transparent: true,
						opacity: 0.4,
					})
				)
				shadow.scale.set(field_zoom * 0.4, field_zoom * 0.4, field_zoom * 0.4)
				shadow.position.y = airs[i].y * 16 * field_zoom - 6 * field_zoom + 0.1
				shadow.rotation.x = deg2rad(-90)
				scene.add(shadow)

				let entity_block = new THREE.Object3D().add(cube)
				entity_block.add(cube.clone())
				entity_block.children[1].visible = false
				entity_block.children[1].position.set(3 * field_zoom * 0.25, 5 * field_zoom * 0.25, -2 * field_zoom * 0.25)
				
				entity_block.position.set(
					pos_x + (airs[i].x - field_wide / 2) * 16 * field_zoom,
					ground_y - (j+1) * 16 * field_zoom,
					pos_z + (airs[i].z - field_wide / 2) * 16 * field_zoom
				)
				scene.add(entity_block)

				let drop = new Debris({
					position: entity_block.position,
					sprite: entity_block,
					angle: Math.random() * 360,
					fall_bias: 100,
					begin_height: -0,
					spread: 1.1,
					begin_pos: 0.2 + Math.random() * 0.1,
					curve_height: 0.1,
					scale: 1,
					maxfall: ground_y + airs[i].y * 16 * field_zoom,
					nohide: true,
				});

				drop.count = 1
				drop.vacume = Math.random() * 1
				drop.shadow = shadow
				drop.end_y = airs[i].y * 16 * field_zoom - 6 * field_zoom + 0.1
				drop.base_pos = drop.sprite.position.y

				item_entities.push(drop)
			}
		}
	}

	Render(scene)

	// ===========================================================================================================

	function Render(scene) {

		// initilalize camera
		var viewSize = 280;

		var aspectRatio = width / height;
		camera = new THREE.OrthographicCamera(
			-aspectRatio * viewSize / 2,
			aspectRatio * viewSize / 2,
			viewSize / 2,
			-viewSize / 2,
			-1000,
			1000
		);

		camera.position.set(viewSize, viewSize * Math.tan(deg2rad(39.23)), viewSize); // Isometric position
		camera.lookAt(scene.position);
		camera.zoom = zoom;
		camera.updateProjectionMatrix();
		controls = new THREE.OrbitControls(camera, renderer.domElement);

		// Lighting -------------------------------------------------------------------------------------

		const dirLight = new THREE.DirectionalLight(night ? 0x9999cc: 0xFFFFFF);
		//dirLight.intensity = 0.435;
		dirLight.intensity = 0.46;
		dirLight.position.set(-1, 2.25, 1.24).normalize();
		dirLight.castShadow = true;
		dirLight.shadow.mapSize.width = 2048;
		dirLight.shadow.mapSize.height = 2048;
		var d = 50;
		dirLight.shadow.camera.left = -d;
		dirLight.shadow.camera.right = d;
		dirLight.shadow.camera.top = d;
		dirLight.shadow.camera.bottom = -d;
		dirLight.shadow.camera.far = 3500;
		dirLight.shadow.bias = -0.0001;
		scene.add(dirLight);
		var ambientLight = new THREE.AmbientLight(night ? 0x333333: 0xFCFCFF);
		ambientLight.intensity = 0.618;
		scene.add( ambientLight );

		// Initialize Timeline Control -------------------------------------------------------------------------------------

		timeline = new TimeLineControl()

		timeline.addEvent({
			start: 0,
			interval: 0.01,
      reverse: true,
			reset: function () {
				tnt_block.forEach(block => { if (block) block.visible = true })
				tnt_entity.object.visible = false
				shadow.visible = false
				surface_blocks.forEach(grass => { grass.visible = true; })
				breaks.forEach(debri => { debri.sprite.visible = false; })
				tnt_ignite_smokes.forEach(smoke => { smoke.sprite.visible = false; })
				item_entities.forEach( drop_item => {
					drop_item.sprite.visible = false;
					drop_item.shadow.visible = false;
				})
			},
			over: function(){
				tnt_block.forEach(block => { if (block) block.visible = false })
				breaks.forEach(debri => { debri.sprite.visible = true; })
			}
		})

		timeline.addEvent({
			start: 0.04,
			interval: 0.2,
      reverse: true,
			reset: function () {
				tnt_entity.object.visible = true;
				tnt_entity.object.scale.set(field_zoom, field_zoom, field_zoom)
				tnt_entity.object.position.set(pos_x, ground_y, pos_z);
				shadow.visible = true;
				shadow.position.set(pos_x, pos_y + 0.01, pos_z);
			},
			process: function (ticks) {
				let tick = (1 - ticks)
				tnt_entity.object.position.set(
					pos_x + 1 * tick,
					ground_y + Math.sin(tick * Math.PI) * 8,
					pos_z + -1.5 * tick
				);
				shadow.position.set(
					pos_x + 1 * tick,
					pos_y + 0.01,
					pos_z + -1.5 * tick
				);
			},
			over: function () {
				tnt_entity.object.position.set(
					pos_x + 1,
					ground_y,
					pos_z + -1.5
				);
			}
		})

		timeline.addEvent({
			start: 0.01,
			interval: 0.5,
      reverse: true,
			process: function (ticks) {
				breaks.forEach(debri => { debri.process(ticks) })
			},
			over: function () {
				breaks.forEach(debri => { debri.sprite.visible = false; })
			}
		})

		timeline.addEvent({
			start: 0.01,
			interval: 1.45,
      reverse: true,
			process: function (ticks) {
				let tick = Math.floor(ticks * 16 % 2)
				tnt_entity.loader.meshes.forEach(mesh => {
					if (tick) {
						mesh.material = tnt_entity.whiteMaterials
					} else {
						mesh.material = tnt_entity.loader.materials
					}
				})
			},
		})
		

		timeline.addEvent({
			start: 1.45 + 0.01,
			interval: 0.05,
      reverse: true,
			reset: function(){
				tnt_entity.loader.meshes.forEach(mesh => {
					mesh.material = tnt_entity.whiteMaterials
				})
			},
			process: function (ticks) {
				let tick = 1- ticks
				let infrate = (1 + tick*0.15)
				tnt_entity.object.scale.set(field_zoom * infrate, field_zoom * infrate, field_zoom * infrate)
			},
			over: function () {
				tnt_entity.object.visible = false
				shadow.visible = false
				surface_blocks.forEach(grass => { grass.visible = false; })
			}
		})

		/// ignit smoke

		tnt_ignite_smokes.forEach((smoke, i) => {
			timeline.addEvent({
				start: 0.02 + smoke.start_time,
				interval: 0.4 + Math.random() * 0.1,
				reset: function () { smoke.reset() },
        reverse: true,
				process: function (ticks) {
					smoke.process(ticks)
				},
				over: function () {
					smoke.sprite.visible = false
				},
			})
		})

		// bomb smokes ------------------

		if (isOld) {
			// Stars
			timeline.addEvent({
				start: 1.5,
				interval: 0.0001,
        reverse: true,
				over: function () {
					fireworks.forEach(firework => {
						firework.process(1)
						firework.sprite.material.map = smoke_textures[7];
					})
				}
			})
			timeline.addEvent({
				start: 1.5 + 0.02,
				interval: 1,
        reverse: true,
				process: function (ticks) {
					fireworks.forEach(firework => { firework.process(ticks) })
				},
				over: function () {
					fireworks.forEach(firework => { firework.sprite.visible = false })
				},
			})


			timeline.addEvent({
				start: 1.5 + 0.02,
				interval: 0.2,
        reverse: true,
				process: function (ticks) {
					blacksmokes.forEach(firework => { firework.process(ticks) })
				},
				over: function () {
					blacksmokes.forEach(firework => { firework.sprite.visible = false })
				},
			})
		}

		for (let i = 0; i < bomb.length; i++) {
			timeline.addEvent({
				start: 1.5 + 0.02 + bomb[i].start,
				interval: bomb[i].interval,
        reverse: true,
				process: function (ticks) {
					bomb[i].sprite.process(ticks)
				},
				over: function () {
					bomb[i].sprite.over()
				}
			})
		}

		let recalc_pos = function(position){
			return position.clone().add(new THREE.Vector3(-32, -(-8 + 8 * field_zoom), -32)).divide(new THREE.Vector3(16 * field_zoom, 16 * field_zoom, 16 * field_zoom)).add(new THREE.Vector3(field_wide / 2, 0, field_wide / 2))
		}

		timeline.addEvent({
			start: 1.5,
			interval: 0.2,
      reverse: true,
			reset: function(){
				item_entities.forEach( drop_item => {
					drop_item.sprite.children[0].position.set(0, 0, 0)
					drop_item.sprite.children[1].visible = false
					drop_item.shadow.visible = true
					drop_item.count = 1
				})
			},
			process: function(ticks){
				item_entities.forEach( drop_item => {
					drop_item.process(ticks)
					drop_item.shadow.position.x = drop_item.sprite.position.x
					drop_item.shadow.position.z = drop_item.sprite.position.z
				})
			},
			over: function(){
				item_entities.forEach( drop_item => {
					// recalcurate y position
					let raw_pos = recalc_pos(drop_item.sprite.position)
					let yy = check(Math.floor(raw_pos.x + 0.5), Math.floor(raw_pos.z + 0.5))
					drop_item.sprite.position.y = ground_y + yy * 16 * field_zoom
					drop_item.shadow.position.y = yy * 16 * field_zoom - 6 * field_zoom + 0.1
					drop_item.base_pos = drop_item.sprite.position.y

					for(let i=0; i<item_entities.length; i++){
						let target = item_entities[i]
						if(target == drop_item || target.visible == false) continue;
						let distance = drop_item.sprite.position.distanceTo(target.sprite.position) / field_zoom
						if(distance < 24){
							let winner, looser
							if(drop_item.count > target.count || drop_item.vacume > target.vacume){
								winner = drop_item
								looser = target
							} else {
								winner = target
								looser = drop_item
							}
							winner.count ++
							winner.sprite.children[0].position.set(-2 * field_zoom * 0.25, 0, 2 * field_zoom * 0.25)
							winner.sprite.children[1].visible = true
							looser.sprite.visible = false
							looser.shadow.visible = false
						}
					}
				})
			}
		})

		timeline.addEvent({
			start: 1.5 + 0.05,
			interval: 4 - (1.5 + 0.2),
      reverse: true,
			process: function(ticks){
				item_entities.forEach( drop_item => {
					let fuwafuwa = Math.sin(((ticks + drop_item.vacume) % 1) * Math.PI * 2) * 4 * field_zoom
					drop_item.sprite.position.y = drop_item.base_pos + fuwafuwa
					drop_item.sprite.rotation.y = deg2rad(((ticks + drop_item.vacume) % 1) * 360)
				})
			}
		})

		timeline.addEvent({
			start: 0,
			interval: 4
		})

		// Run tick -------------------------------------------------------------------------------------

		$("#test").on("click", function () {

			life_time = 0
			test = true;
      begin_sec = new Date().getTime()
		});

		life_time = 0

		tick()

		function tick() {

			if (test) {
				if (life_time < 1) {
          let now = (new Date().getTime() - begin_sec) * 0.001;
          let tick = now / 10;
					life_time = tick
					timeline.process(life_time)
				} else {
					life_time = 0
					timeline.process(0.000001)
					test = false
				}
			}

			renderer.render(scene, camera);
			requestAnimationFrame(tick);
		}
	}
});

// MISC functions ==========================================================================================


function createSmoke(age) {
	var img = document.querySelector("#particles");
	let canvas = document.createElement("canvas");
	canvas.setAttribute("width", 8);
	canvas.setAttribute("height", 8);
	let ctx = canvas.getContext('2d');
	ctx.imageSmoothingEnabled = false;
	ctx.clearRect(0, 0, 8, 8);
	if (img) {
		ctx.drawImage(img, 8 * age, 0, 8, 8, 0, 0, 8, 8);
	}

	let texture = new THREE.CanvasTexture(canvas);
	texture.magFilter = THREE.NearestFilter;//.LinearFilter;
	texture.minFilter = THREE.NearestFilter;
	return texture;
}

function createExplosion(age) {
	var img = document.querySelector("#explosion_" + age);
	let canvas = document.createElement("canvas");
	let w = 32
	canvas.setAttribute("width", w);
	canvas.setAttribute("height", w);
	let ctx = canvas.getContext('2d');
	ctx.imageSmoothingEnabled = false;
	ctx.clearRect(0, 0, w, w);
	if (img) {
		ctx.drawImage(img, 0, 0);
	}

	let texture = new THREE.CanvasTexture(canvas);
	texture.magFilter = THREE.NearestFilter;//.LinearFilter;
	texture.minFilter = THREE.NearestFilter;
	return texture;
}


function CreateBreakTextures(elmname) {
	let img = document.querySelector(elmname);
	let points = [];
	let tex = [];

	let chk_rect = (pos) => {
		let found = false;
		points.forEach(p => {
			found |= (2 * Math.abs(p.x - pos.x) > 8) && (2 * Math.abs(p.y - pos.y) > 8)
		})
		return found
	}

	for (let i = 0; i < 8; i++) {
		// find no collision rectangle
		let pos = {};
		do {
			pos.x = Math.floor(Math.random() * 12)
			pos.y = Math.floor(Math.random() * 12)
		}
		while (chk_rect(pos));

		points.push(pos);

		let canvas = createCanvas(4, 4);
		canvas.context.drawImage(img, pos.x, pos.y, 4, 4, 0, 0, 4, 4);

		let texture = new THREE.CanvasTexture(canvas.canvas);
		texture.magFilter = THREE.NearestFilter;//.LinearFilter;
		texture.minFilter = THREE.NearestFilter;

		tex.push(texture);
	}

	return tex
}

//-------------------------------------------------------

// smoke particle controller

var Smoke = function (option) {
	this.start_pos = { x: option.x, y: option.y, z: option.z };
	this.type = option.type
	this.prev_type = -1;
	this.scale = option.scale;
	this.dir = option.dir;
	this.str = option.str;
	this.rise = option.rise;
	this.color = new THREE.Color("rgb(" + option.color + ", " + option.color + ", " + option.color + ")");

	this.textures = smoke_textures

	this.sprite = new THREE.Sprite(new THREE.SpriteMaterial({ map: this.textures[7], color: this.color, alphaTest: 0.5 }));
	this.sprite.scale.set(this.scale, this.scale, this.scale)
	this.sprite.position.set(this.start_pos.x, this.start_pos.y, this.start_pos.z)
	this.sprite.visible = false
	return this;
}

Smoke.prototype.process = function (ticks) {
	if (ticks > 0.1) {
		let tmp_age = Math.floor((ticks - 0.2) * 1.25 * 8 % 8)
		this.sprite.visible = true;
		if (this.prev_type != tmp_age) {
			this.prev_type = tmp_age;
			let type = Math.floor(this.type + tmp_age)
			type = type < 8 ? type : 0;
			this.sprite.material.map = this.textures[(7 - type) % 9];
		}
		this.calc_pos(ticks)
	} else {
		this.sprite.visible = false;
	}
}

Smoke.prototype.calc_pos = function (ticks) {
	let minus = (1 - ticks) * 0.4
	this.sprite.position.set(
		this.start_pos.x + (Math.cos(this.dir) * minus * 12 * this.str),
		this.start_pos.y + (Math.pow(this.rise, minus) * 10),
		this.start_pos.z + (Math.sin(this.dir) * minus * 12 * this.str)
	);
}

Smoke.prototype.reset = function () {
	this.sprite.scale.set(this.scale, this.scale, this.scale)
	this.sprite.position.set(this.start_pos.x, this.start_pos.y, this.start_pos.z)
	this.sprite.visible = false
	this.sprite.material.map = this.textures[this.type];
}

//-------------------------------------------------------
// for white smoke

var Firework = function (option, object) {
	if (!object) {
		this.pos = option.pos;
	  this.origin = option.origin;
		this.scale = option.scale;
		this.durability = option.durability;
		this.down = option.down;
		this.limit = option.limit;
		this.color = new THREE.Color("rgb(" + option.color + ", " + option.color + ", " + option.color + ")");
		this.age = 7;
		this.sprite = new THREE.Sprite(new THREE.SpriteMaterial({ map: smoke_textures[this.age], color: this.color, alphaTest: 0.5 }));
		this.sprite.scale.set(this.scale, this.scale, this.scale);
		this.sprite.visible = false;
		this.enable = true;
	} else {
		this.pos = object.pos.clone();
    this.origin = object.clone();
		this.color = new THREE.Color("rgb(" + option.color + ", " + option.color + ", " + option.color + ")");
		this.scale = object.scale;
		this.durability = object.durability;
		this.down = object.down;
		this.limit = object.limit;
		this.age = object.age;
		this.sprite = new THREE.Sprite(new THREE.SpriteMaterial({ map: smoke_textures[this.age], color: this.color, alphaTest: 0.5 }));
		this.sprite.scale.set(this.scale, this.scale, this.scale);
		this.sprite.visible = false;
		this.enable = true;
	}
	return this;
}

Firework.prototype.clone = function () {
	let copy = new Firework(undefined, this)
	return copy;
}

Firework.prototype.process = function (ticks) {
	if (ticks - this.durability > 0) {
		let tick = 1 - (ticks - this.durability) / (1 - this.durability)
		let sig = (1 - sigmoid(tick * 12))

		let xx = this.origin.x + this.pos.x * sig
		let yy = this.origin.y + this.pos.y * sig + tick * this.down
		let zz = this.origin.z + this.pos.z * sig

		this.sprite.position.set(xx, yy, zz)
		this.sprite.visible = true;

		let age = Math.floor(tick / (1 - this.durability) * 8)
		age = age > 7 ? 7 : age

		this.sprite.material.map = smoke_textures[age];

	} else {
		this.sprite.visible = false;
		this.enable = false;
	}
}


//-------------------------------------------------------
// for black smoke
var BlackSmoke = function (option) {
	this.pos = option.pos;
	this.origin = option.origin;
	this.scale = option.scale;
	this.durability = option.durability;
	this.down = option.down;
	this.limit = option.limit;
	this.color = new THREE.Color("rgb(" + option.color + ", " + option.color + ", " + option.color + ")");
	this.age = 7;
	this.sprite = new THREE.Sprite(new THREE.SpriteMaterial({ map: smoke_textures[this.age], color: this.color, alphaTest: 0.5 }));
	this.sprite.scale.set(this.scale, this.scale, this.scale);
	this.sprite.visible = false;
	this.enable = true;

	return this;
}

BlackSmoke.prototype.process = function (ticks) {
	if (ticks - this.durability > 0) {
		let tick = 1 - (ticks - this.durability) / (1 - this.durability)
		let sig = (1 - sigmoid(tick * 2))

		let xx = this.origin.x + this.pos.x * sig
		let yy = this.origin.y + this.pos.y * sig + tick * this.down
		let zz = this.origin.z + this.pos.z * sig

		this.sprite.position.set(xx, yy, zz)
		this.sprite.visible = true;

		let age = Math.floor(tick / (1 - this.durability) * 8)
		age = age > 7 ? 7 : age

		this.sprite.material.map = smoke_textures[age];

	} else {
		this.sprite.visible = false;
		this.enable = false;
	}
}
//--------------------------------------------------------------------
// for round shaped smoke particles
var Explode = function (option) {
	this.color = new THREE.Color("rgb(" + option.color + ", " + option.color + ", " + option.color + ")");
	this.sprite = new THREE.Sprite(new THREE.SpriteMaterial({ map: explosion_textures[0], color: this.color, alphaTest: 0.5 }));
	this.sprite.scale.set(option.scale, option.scale, option.scale);
	this.sprite.position.set(option.pos.x, option.pos.y, option.pos.z);
	this.sprite.visible = false;
}
Explode.prototype.process = function (ticks) {
	this.sprite.visible = true;
	this.sprite.material.map = explosion_textures[Math.floor((1 - ticks) * 16)]
}
Explode.prototype.over = function () {
	this.sprite.visible = false;
}

//------------------------------
// for break particles

var Debris = function (setting) {
	this.pos = setting.position.clone();
	this.tex = Math.floor(setting.tex % 8);
	this.angle = setting.angle;
	this.fall_bias = setting.fall_bias;
	this.begin_height = setting.begin_height;
	this.spread = setting.spread;
	this.begin_pos = setting.begin_pos;
	this.curve_height = setting.curve_height;
	this.scale = setting.scale;
	this.maxfall = setting.maxfall;
	this.durability = 0.3 + Math.random() * 0.3;
	this.nohide = setting.nohide;
	if(!setting.sprite){
		this.sprite = new THREE.Sprite(new THREE.SpriteMaterial({ map: break_tex[this.tex], alphaTest: 0.9 }));
	} else {
		this.sprite = setting.sprite
	}
	this.sprite.scale.set(this.scale, this.scale, this.scale);
	this.sprite.position.set(this.pos.x, this.pos.y, this.pos.z)
	this.sprite.visible = false;
	return this;
}

Debris.prototype.process = function (ticks) {
	if (ticks - this.durability > 0) {
		let tick = 1 - ticks

		let y = this.pos.y - (this.begin_height + Math.pow(this.begin_pos - tick, 2) * this.fall_bias) + this.begin_pos * this.curve_height
		if (y < this.maxfall) {
			y = this.maxfall
		}
		let x = tick * this.spread

		let xx = Math.sin(deg2rad(this.angle)) * x
		let zz = Math.cos(deg2rad(this.angle)) * x

		this.sprite.position.set(this.pos.x + xx, y, this.pos.z + zz)
		this.sprite.visible = true;

		let tex = (ticks - this.durability) / (1 - this.durability)
	} else {
		if(!this.nohide)
			this.sprite.visible = false;
	}
}

Debris.prototype.over = function () {
	if(!this.nohide)
		this.sprite.visible = false;
}

//-------------------------------------------------------------------

function sigmoid(x) {
	return (1 / (1 + Math.pow(Math.E, x)))
}

function faceRotate(mesh, layer, idx, rot) {
	if (rot == 0) return
	let convert = [{}, { "0": 2, "1": 0, "2": 3, "3": 1, }, { "0": 3, "1": 2, "2": 1, "3": 0, }, { "0": 1, "1": 3, "2": 0, "3": 2, }]
	let set = (uv, mx, my, rot) => {
		let c = convert[rot][((uv.x == mx.max ? 1 : 0) << 1) | (uv.y == my.max ? 1 : 0)]
		uv.x = c >> 1 ? mx.max : mx.min
		uv.y = c & 1 ? my.max : my.min
	}
	let mx = getMinMax(mesh.geometry.faceVertexUvs[layer][idx], 1)
	let my = getMinMax(mesh.geometry.faceVertexUvs[layer][idx])
	mesh.geometry.faceVertexUvs[layer][idx].forEach(uv => set(uv, mx, my, rot))
	mesh.geometry.uvsNeedUpdate = true
}

function faceFlip(mesh, layer, idx) {
	let mx = getMinMax(mesh.geometry.faceVertexUvs[layer][idx], 1)
	mesh.geometry.faceVertexUvs[layer][idx].forEach(uv => { uv.x = uv.x == mx.min ? mx.max : mx.min })
	mesh.geometry.uvsNeedUpdate = true
}

function faceVFlip(mesh, layer, idx) {
	let mx = getMinMax(mesh.geometry.faceVertexUvs[layer][idx])
	mesh.geometry.faceVertexUvs[layer][idx].forEach(uv => { uv.y = uv.y == mx.min ? mx.max : mx.min })
	mesh.geometry.uvsNeedUpdate = true
}

function addVertexUVs(mesh) {
	mesh.geometry.faceVertexUvs[1] = [];

	for (let j = 0; j < mesh.geometry.faceVertexUvs[0].length; j++) {

		let uvs = mesh.geometry.faceVertexUvs[0][j];
		let uvsCopy = [];

		for (let k = 0; k < uvs.length; k++) {

			uvsCopy.push(uvs[k].clone());

		}
		mesh.geometry.faceVertexUvs[1].push(uvsCopy);
	}
}

/*
	0: N
	1: NE
	2: E
	3: SE
	4: S
	5: SW
	6: W
	7: NW
*/
function createAOMap(sides) {

	let level = []
	for (let i = 0; i < 4; i++) {
		let j = i * 2
		level.push(vertexAO(sides[j], sides[(j - 2 + 8) % 8], sides[(j - 1 + 8) % 8]))
	}

	let canvas = document.createElement("canvas");
	canvas.setAttribute("width", 2);
	canvas.setAttribute("height", 2);
	let ctx = canvas.getContext('2d');

	const cols = [0.6 * 255, 0.7 * 255, 0.8 * 255, 1.0 * 255]

	let col = cols[level[0]]
	ctx.fillStyle = "rgb(" + col + ", " + col + ", " + col + ")"
	ctx.fillRect(0, 0, 1, 1);

	col = cols[level[1]]
	ctx.fillStyle = "rgb(" + col + ", " + col + ", " + col + ")"
	ctx.fillRect(1, 0, 1, 1);

	col = cols[level[2]]
	ctx.fillStyle = "rgb(" + col + ", " + col + ", " + col + ")"
	ctx.fillRect(1, 1, 1, 1);

	col = cols[level[3]]
	ctx.fillStyle = "rgb(" + col + ", " + col + ", " + col + ")"
	ctx.fillRect(0, 1, 1, 1);

	let texture = new THREE.CanvasTexture(canvas);
	texture.magFilter = THREE.LinearFilter;
	texture.minFilter = THREE.LinearFilter;

	return texture
}
function vertexAO(side1, side2, corner) {
	if (side1 && side2) {
		return 0
	}
	return 3 - (side1 + side2 + corner)
}

var tnt_model = "data:application/json;base64,ew0KCSJtZXRhIjogew0KCQkiZm9ybWF0X3ZlcnNpb24iOiAiMy4wIiwNCgkJIm1vZGVsX2Zvcm1hdCI6ICJqYXZhX2Jsb2NrIiwNCgkJImJveF91diI6IGZhbHNlDQoJfSwNCgkibmFtZSI6ICJ0bnQiLA0KCSJlbGVtZW50cyI6IFsNCgkJew0KCQkJIm5hbWUiOiAiY3ViZSIsDQoJCQkiZnJvbSI6IFsNCgkJCQkwLA0KCQkJCTAsDQoJCQkJMA0KCQkJXSwNCgkJCSJ0byI6IFsNCgkJCQkxNiwNCgkJCQkxNiwNCgkJCQkxNg0KCQkJXSwNCgkJCSJhdXRvdXYiOiAxLA0KCQkJImNvbG9yIjogMywNCgkJCSJvcmlnaW4iOiBbDQoJCQkJOCwNCgkJCQk4LA0KCQkJCTgNCgkJCV0sDQoJCQkiZmFjZXMiOiB7DQoJCQkJIm5vcnRoIjogew0KCQkJCQkidXYiOiBbDQoJCQkJCQkwLA0KCQkJCQkJMCwNCgkJCQkJCTE2LA0KCQkJCQkJMTYNCgkJCQkJXSwNCgkJCQkJInRleHR1cmUiOiAwDQoJCQkJfSwNCgkJCQkiZWFzdCI6IHsNCgkJCQkJInV2IjogWw0KCQkJCQkJMCwNCgkJCQkJCTAsDQoJCQkJCQkxNiwNCgkJCQkJCTE2DQoJCQkJCV0sDQoJCQkJCSJ0ZXh0dXJlIjogMA0KCQkJCX0sDQoJCQkJInNvdXRoIjogew0KCQkJCQkidXYiOiBbDQoJCQkJCQkwLA0KCQkJCQkJMCwNCgkJCQkJCTE2LA0KCQkJCQkJMTYNCgkJCQkJXSwNCgkJCQkJInRleHR1cmUiOiAwDQoJCQkJfSwNCgkJCQkid2VzdCI6IHsNCgkJCQkJInV2IjogWw0KCQkJCQkJMCwNCgkJCQkJCTAsDQoJCQkJCQkxNiwNCgkJCQkJCTE2DQoJCQkJCV0sDQoJCQkJCSJ0ZXh0dXJlIjogMA0KCQkJCX0sDQoJCQkJInVwIjogew0KCQkJCQkidXYiOiBbDQoJCQkJCQkwLA0KCQkJCQkJMCwNCgkJCQkJCTE2LA0KCQkJCQkJMTYNCgkJCQkJXSwNCgkJCQkJInRleHR1cmUiOiAxDQoJCQkJfSwNCgkJCQkiZG93biI6IHsNCgkJCQkJInV2IjogWw0KCQkJCQkJMCwNCgkJCQkJCTAsDQoJCQkJCQkxNiwNCgkJCQkJCTE2DQoJCQkJCV0sDQoJCQkJCSJ0ZXh0dXJlIjogMQ0KCQkJCX0NCgkJCX0sDQoJCQkidXVpZCI6ICIwZjEzMmY0ZS1iYjZiLWZlZDUtZjA5ZC0wZDIxZmFiMGE0NDYiDQoJCX0NCgldLA0KCSJvdXRsaW5lciI6IFsNCgkJIjBmMTMyZjRlLWJiNmItZmVkNS1mMDlkLTBkMjFmYWIwYTQ0NiINCgldLA0KCSJ0ZXh0dXJlcyI6IFsNCgkJew0KCQkJInBhdGgiOiAiIiwNCgkJCSJuYW1lIjogInRudF9zaWRlLnBuZyIsDQoJCQkiZm9sZGVyIjogImJsb2NrIiwNCgkJCSJuYW1lc3BhY2UiOiAibWluZWNyYWZ0IiwNCgkJCSJpZCI6ICJzaWRlIiwNCgkJCSJwYXJ0aWNsZSI6IHRydWUsDQoJCQkibW9kZSI6ICJsaW5rIiwNCgkJCSJzYXZlZCI6IHRydWUsDQoJCQkidXVpZCI6ICJiMjFiNDU2Zi0yZDBjLWNhODYtOTI2OS1jMzUyZDZlY2U4NzEiDQoJCX0sDQoJCXsNCgkJCSJwYXRoIjogIiIsDQoJCQkibmFtZSI6ICJ0bnRfdG9wLnBuZyIsDQoJCQkiZm9sZGVyIjogImJsb2NrIiwNCgkJCSJuYW1lc3BhY2UiOiAibWluZWNyYWZ0IiwNCgkJCSJpZCI6ICJ0b3AiLA0KCQkJInBhcnRpY2xlIjogZmFsc2UsDQoJCQkibW9kZSI6ICJsaW5rIiwNCgkJCSJzYXZlZCI6IHRydWUsDQoJCQkidXVpZCI6ICJkNjFjM2M5Zi1hZjcwLTEzMWMtZTBmNy1iZmU4MDFkOGM3MDMiDQoJCX0NCgldDQp9";

var pumpkin_model = "data:application/json;base64,eyJtZXRhIjp7ImZvcm1hdF92ZXJzaW9uIjoiMy4wIiwibW9kZWxfZm9ybWF0IjoiamF2YV9ibG9jayIsImJveF91diI6ZmFsc2V9LCJuYW1lIjoiY3ViZV90b3Bfc2lkZV9mYWNlIiwicGFyZW50IjoiYmxvY2svYmxvY2siLCJhbWJpZW50b2NjbHVzaW9uIjp0cnVlLCJyZXNvbHV0aW9uIjp7IndpZHRoIjoxNiwiaGVpZ2h0IjoxNn0sImVsZW1lbnRzIjpbeyJuYW1lIjoiY3ViZSIsImZyb20iOlswLDAsMF0sInRvIjpbMTYsMTYsMTZdLCJhdXRvdXYiOjAsImNvbG9yIjo3LCJvcmlnaW4iOls4LDgsOF0sImZhY2VzIjp7Im5vcnRoIjp7InV2IjpbMCwwLDE2LDE2XSwidGV4dHVyZSI6MCwiY3VsbGZhY2UiOiJub3J0aCJ9LCJlYXN0Ijp7InV2IjpbMCwwLDE2LDE2XSwidGV4dHVyZSI6MCwiY3VsbGZhY2UiOiJlYXN0In0sInNvdXRoIjp7InV2IjpbMCwwLDE2LDE2XSwidGV4dHVyZSI6MSwiY3VsbGZhY2UiOiJzb3V0aCJ9LCJ3ZXN0Ijp7InV2IjpbMCwwLDE2LDE2XSwidGV4dHVyZSI6MCwiY3VsbGZhY2UiOiJ3ZXN0In0sInVwIjp7InV2IjpbMCwwLDE2LDE2XSwidGV4dHVyZSI6MiwiY3VsbGZhY2UiOiJ1cCJ9LCJkb3duIjp7InV2IjpbMCwwLDE2LDE2XSwidGV4dHVyZSI6MiwiY3VsbGZhY2UiOiJkb3duIn19LCJ1dWlkIjoiNTVkYTZkZWQtNTNjOC01ZDdmLWE1NTQtN2RjNDg2ZjY4MWNlIn1dLCJvdXRsaW5lciI6WyI1NWRhNmRlZC01M2M4LTVkN2YtYTU1NC03ZGM0ODZmNjgxY2UiXSwidGV4dHVyZXMiOlt7InBhdGgiOiJDOlxcVXNlcnNcXERJQU5BMTFcXEFwcERhdGFcXFJvYW1pbmdcXC5taW5lY3JhZnRcXHZlcnNpb25zXFwxLjE0XFxhc3NldHNcXG1pbmVjcmFmdFxcdGV4dHVyZXNcXGJsb2NrXFxwdW1wa2luX3NpZGUucG5nIiwibmFtZSI6InB1bXBraW5fc2lkZS5wbmciLCJmb2xkZXIiOiJibG9jayIsIm5hbWVzcGFjZSI6Im1pbmVjcmFmdCIsImlkIjoic2lkZSIsInBhcnRpY2xlIjpmYWxzZSwibW9kZSI6ImxpbmsiLCJzYXZlZCI6dHJ1ZSwidXVpZCI6ImZiZDMxZmYzLTQ4NjYtMGI0Yy0xMjNiLTk0MDQxZDI5ZDY0NiJ9LHsicGF0aCI6IkM6XFxVc2Vyc1xcRElBTkExMVxcQXBwRGF0YVxcUm9hbWluZ1xcLm1pbmVjcmFmdFxcdmVyc2lvbnNcXDEuMTRcXGFzc2V0c1xcbWluZWNyYWZ0XFx0ZXh0dXJlc1xcYmxvY2tcXGNhcnZlZF9wdW1wa2luLnBuZyIsIm5hbWUiOiJjYXJ2ZWRfcHVtcGtpbi5wbmciLCJmb2xkZXIiOiJibG9jayIsIm5hbWVzcGFjZSI6Im1pbmVjcmFmdCIsImlkIjoiZnJvbnQiLCJwYXJ0aWNsZSI6ZmFsc2UsIm1vZGUiOiJsaW5rIiwic2F2ZWQiOnRydWUsInV1aWQiOiI2ZTlmOTgwNC04ZGM3LWE3MWYtZjJiZi1mODJmMTViNzVkODcifSx7InBhdGgiOiJDOlxcVXNlcnNcXERJQU5BMTFcXEFwcERhdGFcXFJvYW1pbmdcXC5taW5lY3JhZnRcXHZlcnNpb25zXFwxLjE0XFxhc3NldHNcXG1pbmVjcmFmdFxcdGV4dHVyZXNcXGJsb2NrXFxwdW1wa2luX3RvcC5wbmciLCJuYW1lIjoicHVtcGtpbl90b3AucG5nIiwiZm9sZGVyIjoiYmxvY2siLCJuYW1lc3BhY2UiOiJtaW5lY3JhZnQiLCJpZCI6InVwIiwicGFydGljbGUiOmZhbHNlLCJtb2RlIjoibGluayIsInNhdmVkIjp0cnVlLCJ1dWlkIjoiZGYxMmI1OTQtZjVkNS05ZTU0LWUzMTctMmEyMjY1NzljYzc5In1dfQ==";
              
            
!
999px

Console