Diep.IO 3D

Turns diep.io into real 3D

Устаревшая версия за 23.09.2021. Перейдите к последней версии.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey, Greasemonkey или Violentmonkey.

Для установки этого скрипта вам необходимо установить расширение, такое как Tampermonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Userscripts.

Чтобы установить этот скрипт, сначала вы должны установить расширение браузера, например Tampermonkey.

Чтобы установить этот скрипт, вы должны установить расширение — менеджер скриптов.

(у меня уже есть менеджер скриптов, дайте мне установить скрипт!)

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

(у меня уже есть менеджер стилей, дайте мне установить скрипт!)

// ==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 } ];

		}

	}

}