Diep.IO 3D

Turns diep.io into real 3D

Versione datata 23/09/2021. Vedi la nuova versione l'ultima versione.

Dovrai installare un'estensione come Tampermonkey, Greasemonkey o Violentmonkey per installare questo script.

You will need to install an extension such as Tampermonkey to install this script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Userscripts per installare questo script.

Dovrai installare un'estensione come ad esempio Tampermonkey per installare questo script.

Dovrai installare un gestore di script utente per installare questo script.

(Ho già un gestore di script utente, lasciamelo installare!)

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

(Ho già un gestore di stile utente, lasciamelo installare!)

// ==UserScript==
// @name         Diep.IO 3D
// @namespace    http://tampermonkey.net/
// @version      0.0.1
// @description  Turns diep.io into real 3D
// @author       Zertalious (Zert)
// @match        *://diep.io/*
// @icon         https://www.google.com/s2/favicons?domain=diep.io
// @grant        none
// @require      https://unpkg.com/three@latest/build/three.min.js
// @require      https://unpkg.com/three@latest/examples/js/controls/OrbitControls.js
// ==/UserScript==

const OUTLINE_LAYER = 0;
const MAIN_LAYER = 1;

let renderer, scene, camera, canvas;

init();

const tempObject = new THREE.Object3D();
const tempColor = new THREE.Color();

const material = new THREE.MeshToonMaterial( { transparent: true } );
const outlineMaterial = new THREE.MeshBasicMaterial( { transparent: true } );

material.onBeforeCompile = outlineMaterial.onBeforeCompile = function ( shader ) {

	shader.vertexShader = shader.vertexShader.replace( 'void', `

		attribute vec2 scale;
		attribute float alpha;

		varying float vAlpha;

	void` ).replace( '<begin_vertex>', `<begin_vertex>

		if ( scale.x != 0.0 && scale.y != 0.0 ) {

			if ( transformed.x == 1.0 || transformed.x == 0.5 ) {

				transformed.yz *= scale.x;

			} else if ( transformed.x == - 1.0 || transformed.x == - 0.5 ) {

				transformed.yz *= scale.y;

			}

		}

		vAlpha = alpha;

	` );

	shader.fragmentShader = shader.fragmentShader.replace( 'void', `

		varying float vAlpha;

	void` ).replace( '}', `

		gl_FragColor.a *= vAlpha;

	}` );

}

const instances = {};

const array = [ {
	name: 'sphere', 
	geometry: new THREE.SphereGeometry( 1, 16 ), 
	count: 150
}, {
	name: 'cylinder', 
	geometry: new THREE.CylinderGeometry( 0.5, 0.5, 1, 16 ).rotateZ( Math.PI / 2 ), 
	count: 75, 
	hasScaling: true
}, {
	name: 'poly3', 
	geometry: new THREE.CylinderGeometry( 1, 1, 1, 3, 1, false, - Math.PI / 6 ).rotateX( Math.PI / 2 ), 
	count: 75
}, {
	name: 'poly4', 
	geometry: new THREE.BoxGeometry( 1, 1, 1 ), 
	count: 75
}, {
	name: 'poly5', 
	geometry: new THREE.CylinderGeometry( 1, 1, 1, 5, 1, false, Math.PI / 10 ).rotateX( Math.PI / 2 ), 
	count: 40
}, {
	name: 'poly6', 
	geometry: new THREE.CylinderGeometry( 1, 1, 1, 6, 1, false, - Math.PI / 12 ).rotateX( Math.PI / 2 ),
	count: 10
} ];

for ( let i = 0; i < array.length; i ++ ) {

	const { name, geometry, count, hasScaling } = array[ i ];

	if ( hasScaling ) {

		geometry.setAttribute( 'scale', new THREE.InstancedBufferAttribute( new Float32Array( count * 2 ), 2 ) );

	}

	geometry.setAttribute( 'alpha', new THREE.InstancedBufferAttribute( new Float32Array( count ), 1 ) );

	const main = new THREE.InstancedMesh( geometry, material, count );
	main.layers.set( MAIN_LAYER );
	scene.add( main );

	const outline = new THREE.InstancedMesh( geometry, outlineMaterial, count );
	outline.layers.set( OUTLINE_LAYER );
	scene.add( outline );

	main.setColorAt( 0, tempColor );
	outline.setColorAt( 0, tempColor );

	instances[ name ] = {
		main, 
		outline, 
		count, 
		hasScaling, 
		index: 0
	};

}

const stack = [];

function getStack( index ) {

	const result = stack[ stack.length - 1 - index ];

	if ( result ) {

		return result;

	}

	return { name: 'none' };

}

function setObject( name, x, y, z, sx, sy, sz, angle, color, alpha = 1, scaleX = 1, scaleY = 1 ) {

	tempObject.position.set( x, y, z );
	tempObject.scale.set( sx, sy, sz );
	tempObject.rotation.set( 0, 0, angle );

	tempObject.updateMatrix();

	tempColor.set( color );

	const instance = instances[ name ];

	instance.main.setMatrixAt( instance.index, tempObject.matrix );
	instance.main.setColorAt( instance.index, tempColor );

	instance.main.geometry.attributes.alpha.setX( instance.index, alpha );
	instance.outline.geometry.attributes.alpha.setX( instance.index, alpha );

	const outlineSize = 4 / window.innerHeight * ( name === 'sphere' ? 0.7 : 1 );

	if ( instance.hasScaling ) {

		tempObject.scale.x += outlineSize;
		tempObject.scale.y += outlineSize / scaleY;
		tempObject.scale.z += outlineSize / scaleY;

	} else {

		tempObject.scale.addScalar( outlineSize );

	}

	tempObject.updateMatrix();

	tempColor.multiplyScalar( 0.6 );

	instance.outline.setMatrixAt( instance.index, tempObject.matrix );
	instance.outline.setColorAt( instance.index, tempColor );

	if ( instance.hasScaling ) {

		instance.main.geometry.attributes.scale.setXY( instance.index, scaleX, scaleY );
		instance.outline.geometry.attributes.scale.setXY( instance.index, scaleX, scaleY );

	}

	instance.index ++;

	stack.push( { name, x, y, z, sx, sy, sz, angle, color, outlineSize, alpha } );

}

function init() {

	renderer = new THREE.WebGLRenderer( { 
		antialias: true, 
		alpha: true 
	} );

	renderer.autoClear = false;

	renderer.setPixelRatio( window.devicePixelRatio );
	renderer.setSize( window.innerWidth, window.innerHeight );

	renderer.domElement.style.position = 'absolute';
	renderer.domElement.style.left = '0';
	renderer.domElement.style.top = '0';
	renderer.domElement.style.pointerEvents = 'none';

	canvas = document.getElementById( 'canvas' );

	canvas.parentNode.insertBefore( renderer.domElement, canvas.nextSibling );

	scene = new THREE.Scene();

	camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.1, 1000 );

	const oldZ = Math.sin( Math.PI / 3 );
	camera.position.z = oldZ;

	const ambLight = new THREE.AmbientLight( 0xffffff, 0.5 );
	ambLight.layers.set( MAIN_LAYER );
	scene.add( ambLight );

	const dirLight = new THREE.DirectionalLight( 0xffffff, 0.5 );
	dirLight.layers.set( MAIN_LAYER );
	dirLight.position.z = 1;
	scene.add( dirLight );

	const controls = new THREE.OrbitControls( camera, canvas );

	controls.enabled = false;

	window.addEventListener( 'keyup', function ( event ) {

		if ( String.fromCharCode( event.keyCode ) === 'V' ) {

			controls.enabled = ! controls.enabled;

			if ( ! controls.enabled ) {

				camera.position.set( 0, 0, oldZ );
				camera.rotation.set( 0, 0, 0 );

			}

		}

	} );

	window.addEventListener( 'resize', onWindowResize );

}

function onWindowResize() {

	renderer.setSize( window.innerWidth, window.innerHeight );
	
	camera.aspect = window.innerWidth / window.innerHeight;
	camera.updateProjectionMatrix();

}

window.requestAnimationFrame = new Proxy( window.requestAnimationFrame, {
	apply( target, thisArgs, args ) {

		args[ 0 ] = new Proxy( args[ 0 ], {
			apply( target, thisArgs, args ) {

				stack.length = 0;

				tempObject.position.setScalar( 0 );
				tempObject.scale.setScalar( 0 );
				tempObject.rotation.set( 0, 0, 0 );

				tempObject.updateMatrix();

				tempColor.setRGB( 0, 0, 0 );

				for ( let key in instances ) {

					const { main, outline, count, hasScaling } = instances[ key ];

					for ( let i = 0; i < count; i ++ ) {

						main.setMatrixAt( i, tempObject.matrix );
						outline.setMatrixAt( i, tempObject.matrix );

					}

					main.instanceMatrix.needsUpdate = true;
					main.instanceColor.needsUpdate = true;

					outline.instanceMatrix.needsUpdate = true;
					outline.instanceColor.needsUpdate = true;

					if ( hasScaling ) {

						main.geometry.attributes.scale.needsUpdate = true;
						outline.geometry.attributes.scale.needsUpdate = true;

					}

					main.geometry.attributes.alpha.needsUpdate = true;
					outline.geometry.attributes.alpha.needsUpdate = true;

					instances[ key ].index = 0;

				}

				arcCounter = 0;

				Reflect.apply( ...arguments );

				renderer.clear();

				camera.layers.set( OUTLINE_LAYER );

				renderer.render( scene, camera );

				renderer.clearDepth();

				camera.layers.set( MAIN_LAYER );

				renderer.render( scene, camera );

			}
		} );

		return Reflect.apply( ...arguments );

	}
} );

const Context2D = CanvasRenderingContext2D.prototype;

let arcCounter = 0;

Context2D.arc = new Proxy( Context2D.arc, {
	apply( target, thisArgs, args ) {

		if ( args[ 4 ] === Math.PI * 2 ) {

			if ( arcCounter === 0 ) {

				const matrix = thisArgs.getTransform();

				const r = matrix.a / canvas.height;

				const x = ( matrix.e / window.innerWidth - 0.5 ) * camera.aspect;
				const y = 0.5 - matrix.f / window.innerHeight;

				let z = 0;

				const s0 = getStack( 0 );
				const s1 = getStack( 1 );

				if ( s0.name === 'cylinder' && s1.name === 'sphere' && Math.hypot( x - s1.x, y - s1.y ) < 0.001 ) {

					z = s1.sz;

					const index = ( instances.cylinder.index - 1 ) * 16 + 14;

					const newDepth = z + r - s0.sz / 2;

					instances.cylinder.main.instanceMatrix.array[ index ] = newDepth;
					instances.cylinder.outline.instanceMatrix.array[ index ] = newDepth;

				} else myBlock: {

					for ( let i = 0; i < 5; i ++ ) {

						if ( getStack( i ).name !== 'cylinder' ) {

							break myBlock;

						}

					}

					if ( getStack( 0 ).angle !== getStack( 2 ).angle ) {

						break myBlock;

					}

					const a = r - getStack( 0 ).sy;

					for ( let i = 0; i < 5; i ++ ) {

						const index = ( instances.cylinder.index - 1 - i ) * 16 + 14;

						const newDepth = a - a * 2 * i / 4;

						instances.cylinder.main.instanceMatrix.array[ index ] = newDepth;
						instances.cylinder.outline.instanceMatrix.array[ index ] = newDepth;

					}

				}

				checkIfIsMainCanvas( thisArgs, 'sphere' );

				setObject( 
					'sphere',  
					x, 
					y, 
					z, 
					r, 
					r, 
					r, 
					0, 
					thisArgs.fillStyle, 
					thisArgs.globalAlpha
				);

			} else if ( arcCounter === 1 ) {

				tempColor.set( thisArgs.fillStyle );
				instances.sphere.main.setColorAt( instances.sphere.index - 1, tempColor );

				tempColor.multiplyScalar( 0.6 );
				instances.sphere.outline.setColorAt( instances.sphere.index - 1, tempColor );

			}

			arcCounter = ( arcCounter + 1 ) % 3;

		}

		return Reflect.apply( ...arguments );

	}
} );

Context2D.rect = new Proxy( Context2D.rect, {
	apply( target, thisArgs, args ) {

		const matrix = thisArgs.getTransform();

		if ( matrix.b !== 0 && matrix.c !== 0 ) {

			const center = new DOMPoint( 0.5, 0.5 ).matrixTransform( matrix );

			const scaleYZ = Math.hypot( matrix.c, matrix.d ) / canvas.height;

			checkIfIsMainCanvas( thisArgs, 'cylinder' );

			setObject(
				'cylinder', 
				( center.x / canvas.width - 0.5 ) * camera.aspect, 
				0.5 - center.y / canvas.height, 
				0, 
				Math.hypot( matrix.a, matrix.b ) / canvas.height, 
				scaleYZ, 
				scaleYZ, 
				Math.atan2( matrix.c, matrix.d ), 
				thisArgs.fillStyle, 
				thisArgs.globalAlpha
			);

		}

		return Reflect.apply( ...arguments );

	}
} );

const points = [];
let hasCurve = true;

Context2D.beginPath = new Proxy( Context2D.beginPath, {
	apply( target, thisArgs, args ) {

		points.length = 0;
		hasCurve = false;

		return Reflect.apply( ...arguments );

	}
} );

const addPoint = {
	apply( target, thisArgs, [ x, y ] ) {

		points.push( new DOMPoint( x, y ).matrixTransform( thisArgs.getTransform() ) );

		return Reflect.apply( ...arguments );

	}
};

Context2D.moveTo = new Proxy( Context2D.moveTo, addPoint );
Context2D.lineTo = new Proxy( Context2D.lineTo, addPoint );

Context2D.arc = new Proxy( Context2D.arc, {
	apply( target, thisArgs, args ) {

		hasCurve = true;

		return Reflect.apply( ...arguments );

	}
} );

Context2D.fill = new Proxy( Context2D.fill, {
	apply( target, thisArgs, args ) {

		if ( ! hasCurve ) {

			let shouldCreate = true;

			if ( points.length === 6 ) {

				const [ a, b, c ] = points;

				shouldCreate = Math.abs( Math.hypot( a.x - b.x, a.y - b.y ) - Math.hypot( b.x - c.x, b.y - c.y ) ) < 0.01;

			}

			if ( points.length > 2 && points.length < 7 && shouldCreate ) {

				const center = { x: 0, y: 0 };

				const count = points.length;

				for ( let i = 0; i < count; i ++ ) {

					center.x += points[ i ].x;
					center.y += points[ i ].y;

				}

				center.x /= count;
				center.y /= count;

				let s, sx, angle, scaleX, scaleY;

				let name = 'poly' + points.length;

				if ( points.length === 4 ) {

					const [ p0, p1, p2 ] = points;
					const pl = points[ points.length - 1 ];

					scaleX = Math.hypot( p1.x - p2.x, p1.y - p2.y ) / canvas.height;
					scaleY = Math.hypot( p0.x - pl.x, p0.y - pl.y ) / canvas.height;

					const dx = ( p1.x + p2.x ) / 2 - ( p0.x + pl.x ) / 2;
					const dy = ( p1.y + p2.y ) / 2 - ( p0.y + pl.y ) / 2;

					sx = Math.hypot( dx, dy ) / canvas.height;
					angle = Math.atan2( dx, dy ) - Math.PI / 2;

					if ( Math.abs( scaleX - scaleY ) > 0.001 ) {

						s = 1;
						name = 'cylinder';

					} else {

						s = sx = scaleY;

					}

				} else { 

					s = sx = Math.hypot( points[ 0 ].x - center.x, points[ 0 ].y - center.y ) / canvas.height;

					angle = - Math.atan2( points[ 0 ].y - center.y, points[ 0 ].x - center.x );

				}

				checkIfIsMainCanvas( thisArgs, name );

				setObject(
					name, 
					( center.x / canvas.width - 0.5 ) * camera.aspect, 
					0.5 - center.y / canvas.height, 
					0, 
					sx, 
					s, 
					s, 
					angle, 
					thisArgs.fillStyle, 
					thisArgs.globalAlpha, 
					scaleX, 
					scaleY
				);

			}

		}

		return Reflect.apply( ...arguments );

	}
} );

Context2D.drawImage = new Proxy( Context2D.drawImage, {
	apply( target, thisArgs, args ) {

		if ( thisArgs.canvas === canvas && args[ 0 ].objects ) {

			const matrix = thisArgs.getTransform();

			const x = matrix.e / canvas.width;
			const y = matrix.f / canvas.height;

			const sx = Math.hypot( matrix.a, matrix.b );
			const sy = Math.hypot( matrix.c, matrix.d );

			for ( let i = 0; i < args[ 0 ].objects.length; i ++ ) {

				const { name, index } = args[ 0 ].objects[ i ];

				const instance = instances[ name ];

				const ma = instance.main.instanceMatrix.array;
				const oa = instance.outline.instanceMatrix.array;

				const idx = index * 16;

				const ox = ma[ idx + 12 ] / camera.aspect + 0.5;
				const oy = - ma[ idx + 13 ] + 0.5;

				const outlineOldSx = Math.hypot( oa[ idx + 0 ], oa[ idx + 1 ] );
				const outlineOldSy = Math.hypot( oa[ idx + 4 ], oa[ idx + 5 ] );

				const outlineSizeX = outlineOldSx - Math.hypot( ma[ idx + 0 ], ma[ idx + 1 ] );
				const outlineSizeY = outlineOldSy - Math.hypot( ma[ idx + 4 ], ma[ idx + 5 ] );

				ma[ idx + 0 ] *= sx;
				ma[ idx + 1 ] *= sx;
				ma[ idx + 4 ] *= sy;
				ma[ idx + 5 ] *= sy;
				ma[ idx + 10 ] *= sy;

				const nsx = Math.hypot( ma[ idx + 0 ], ma[ idx + 1 ] ) + outlineSizeX;
				const nsy = Math.hypot( ma[ idx + 4 ], ma[ idx + 5 ] ) + outlineSizeY;

				oa[ idx + 0 ] *= nsx / outlineOldSx;
				oa[ idx + 1 ] *= nsx / outlineOldSx;
				oa[ idx + 4 ] *= nsy / outlineOldSy;
				oa[ idx + 5 ] *= nsy / outlineOldSy;
				oa[ idx + 10 ] *= sy;

				ma[ idx + 12 ] = oa[ idx + 12 ] = ( ( ox * sx + x ) - 0.5 ) * camera.aspect;
				ma[ idx + 13 ] = oa[ idx + 13 ] = 0.5 - ( oy * sy + y );

				instance.main.geometry.attributes.alpha.array[ index ] = thisArgs.globalAlpha;
				instance.outline.geometry.attributes.alpha.array[ index ] = thisArgs.globalAlpha;

			}

			delete args[ 0 ][ 'objects' ];

		}

		return Reflect.apply( ...arguments );

	}
} );

function checkIfIsMainCanvas( ctx, name ) {

	if ( ctx.canvas !== canvas ) {

		const { index } = instances[ name ];

		if ( ctx.canvas.objects ) {

			ctx.canvas.objects.push( { name, index } );

		} else {

			ctx.canvas.objects = [ { name, index } ];

		}

	}

}