// ==UserScript==
// @name Diep.IO 3D
// @namespace http://tampermonkey.net/
// @version 0.0.8
// @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/[email protected]/build/three.min.js
// @require https://unpkg.com/[email protected]/examples/js/controls/OrbitControls.js
// @run-at document-start
// ==/UserScript==
const OUTLINE_LAYER = 0;
const MAIN_LAYER = 1;
let canvas = {};
let renderer, scene, camera;
let ortho;
let currentCamera;
let controls;
init();
const tempObject = new THREE.Object3D();
const tempColor = new THREE.Color();
const materialParams = { transparent: true };
let materialIndex = 0;
const outlineMaterial = new THREE.MeshBasicMaterial( materialParams );
const materials = [
new THREE.MeshToonMaterial( materialParams ),
new THREE.MeshLambertMaterial( materialParams ),
new THREE.MeshPhongMaterial( materialParams ),
outlineMaterial
];
function onBeforeCompile( 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;
}` );
}
for ( let i = 0; i < materials.length; i ++ ) {
materials[ i ].onBeforeCompile = onBeforeCompile;
}
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, materials[ materialIndex ], 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() {
window.addEventListener( 'DOMContentLoaded', function () {
canvas = document.getElementById( 'canvas' );
renderer = new THREE.WebGLRenderer( {
antialias: true,
alpha: true
} );
renderer.autoClear = false;
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( canvas.width, canvas.height, false );
controls = new THREE.OrbitControls( camera, canvas );
controls.enabled = false;
window.addEventListener( 'resize', onWindowResize );
} );
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.1, 1000 );
ortho = new THREE.OrthographicCamera( - camera.aspect / 2, camera.aspect / 2, 0.5, - 0.5, 0, 1000 );
currentCamera = camera;
const oldZ = Math.sin( Math.PI / 3 );
camera.position.z = ortho.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 );
window.addEventListener( 'keyup', function ( event ) {
const key = String.fromCharCode( event.keyCode );
if ( key === 'V' ) {
controls.enabled = ! controls.enabled;
if ( ! controls.enabled ) {
camera.position.set( 0, 0, oldZ );
camera.rotation.set( 0, 0, 0 );
controls.target.set( 0, 0, 0 );
ortho.position.set( 0, 0, oldZ );
ortho.rotation.set( 0, 0, 0 );
ortho.zoom = 1;
}
} else if ( key === 'P' ) {
currentCamera = currentCamera === camera ? ortho : camera;
currentCamera.position.copy( controls.object.position );
currentCamera.rotation.copy( controls.object.rotation );
controls.object = currentCamera;
} else if ( key === 'B' ) {
materialIndex = ( materialIndex + 1 ) % materials.length;
for ( let key in instances ) {
instances[ key ].main.material = materials[ materialIndex ];
}
}
} );
}
function onWindowResize() {
renderer.setSize( canvas.width, canvas.height, false );
camera.aspect = canvas.width / canvas.height;
camera.updateProjectionMatrix();
ortho.left = - camera.aspect / 2;
ortho.right = camera.aspect / 2;
ortho.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;
renderCounter = 0;
Reflect.apply( ...arguments );
}
} );
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 / canvas.width - 0.5 ) * camera.aspect;
const y = 0.5 - matrix.f / canvas.height;
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: {
if ( getStack( 0 ).name === 'cylinder' &&
getStack( 1 ).name === 'sphere' &&
getStack( 2 ).name === 'cylinder' &&
getStack( 3 ).name === 'sphere' &&
getStack( 4 ).name === 'cylinder' &&
getStack( 5 ).name === 'poly3' &&
getStack( 6 ).name === 'cylinder' ) {
z = getStack( 5 ).sz / 2;
const tr = getStack( 2 ).sz;
for ( let i = 0; i < 3; i ++ ) {
const index = ( instances.cylinder.index - 1 - i ) * 16 + 14;
const newDepth = z + r - tr / 2;
instances.cylinder.main.instanceMatrix.array[ index ] = newDepth;
instances.cylinder.outline.instanceMatrix.array[ index ] = newDepth;
}
for ( let i = 0; i < 2; i ++ ) {
const index = ( instances.sphere.index - 1 - i ) * 16 + 14;
instances.sphere.main.instanceMatrix.array[ index ] = z;
instances.sphere.outline.instanceMatrix.array[ index ] = z;
}
break 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();
const isTurret = matrix.b !== 0 && matrix.c !== 0;
if ( isTurret || ( thisArgs.canvas === canvas && Math.hypot( matrix.c, matrix.d ) > 100 && thisArgs.globalAlpha === 1 ) ) {
const center = new DOMPoint( 0.5, 0.5 ).matrixTransform( matrix );
const scaleYZ = Math.hypot( matrix.c, matrix.d ) / canvas.height;
const name = isTurret ? 'cylinder' : 'poly4';
checkIfIsMainCanvas( thisArgs, name );
setObject(
name,
( center.x / canvas.width - 0.5 ) * camera.aspect,
0.5 - center.y / canvas.height,
isTurret ? 0 : 0.05,
Math.hypot( matrix.a, matrix.b ) / canvas.height,
scaleYZ,
isTurret ? scaleYZ : 0.1,
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 ) {
if ( points.length > 2 && points.length < 7 ) myBlock: {
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;
if ( points.length === 6 ) {
const d1 = Math.hypot( points[ 0 ].x - center.x, points[ 0 ].y - center.y );
const d2 = Math.hypot( points[ 1 ].x - center.x, points[ 1 ].y - center.y );
if ( Math.abs( d1 - d2 ) > 0.01 ) {
break myBlock;
}
}
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.fillText = new Proxy( Context2D.fillText, {
apply( target, thisArgs, [ text ] ) {
if ( text === 'diep.io' ) {
thisArgs.canvas.isScoreboard = true;
}
return Reflect.apply( ...arguments );
}
} );
let renderCounter = 0;
function render() {
renderCounter ++;
if ( renderCounter > 1 ) {
console.log( renderCounter, '!!!!' );
}
renderer.clear();
currentCamera.layers.set( OUTLINE_LAYER );
renderer.render( scene, currentCamera );
renderer.clearDepth();
currentCamera.layers.set( MAIN_LAYER );
renderer.render( scene, currentCamera );
}
function drawTexts( ctx ) {
const texts = [ 'Diep3D by Zert', 'Also try triep.io & hornex.pro' ];
const s = Math.min( ctx.canvas.width / 1200, ctx.canvas.height / 700 ) * 1.2;
ctx.scale( s, s );
ctx.translate( 10, 10 );
ctx.font = 'bolder 11px Ubuntu';
ctx.fillStyle = '#fff';
ctx.strokeStyle = '#444';
ctx.lineWidth = 2;
ctx.textBaseline = 'top';
ctx.textAlign = 'left';
for ( let i = 0; i < texts.length; i ++ ) {
const text = texts[ i ];
ctx.strokeText( text, 0, 0 );
ctx.fillText( text, 0, 0 );
ctx.translate( 0, 14 );
}
}
Context2D.drawImage = new Proxy( Context2D.drawImage, {
apply( target, thisArgs, args ) {
if ( args[ 0 ].isScoreboard && thisArgs.canvas.width === canvas.width && thisArgs.canvas.height === canvas.height ) {
thisArgs.canvas.hasScoreboard = true;
} else if ( args[ 0 ].hasScoreboard ) {
render();
thisArgs.save();
thisArgs.globalAlpha = 1;
thisArgs.setTransform( 1, 0, 0, 1, 0, 0 );
Reflect.apply( target, thisArgs, [ renderer.domElement, 0, 0, canvas.width, canvas.height ] );
drawTexts( thisArgs );
thisArgs.restore();
}
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 } ];
}
}
}