Diep.IO 3D

Turns diep.io into real 3D

  1. // ==UserScript==
  2. // @name Diep.IO 3D
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.0.8
  5. // @description Turns diep.io into real 3D
  6. // @author Zertalious (Zert)
  7. // @match *://diep.io/*
  8. // @icon https://www.google.com/s2/favicons?domain=diep.io
  9. // @grant none
  10. // @require https://unpkg.com/three@0.130.0/build/three.min.js
  11. // @require https://unpkg.com/three@0.130.0/examples/js/controls/OrbitControls.js
  12. // @run-at document-start
  13. // ==/UserScript==
  14.  
  15. const OUTLINE_LAYER = 0;
  16. const MAIN_LAYER = 1;
  17.  
  18. let canvas = {};
  19.  
  20. let renderer, scene, camera;
  21. let ortho;
  22.  
  23. let currentCamera;
  24.  
  25. let controls;
  26.  
  27. init();
  28.  
  29. const tempObject = new THREE.Object3D();
  30. const tempColor = new THREE.Color();
  31.  
  32. const materialParams = { transparent: true };
  33.  
  34. let materialIndex = 0;
  35.  
  36. const outlineMaterial = new THREE.MeshBasicMaterial( materialParams );
  37.  
  38. const materials = [
  39. new THREE.MeshToonMaterial( materialParams ),
  40. new THREE.MeshLambertMaterial( materialParams ),
  41. new THREE.MeshPhongMaterial( materialParams ),
  42. outlineMaterial
  43. ];
  44.  
  45. function onBeforeCompile( shader ) {
  46.  
  47. shader.vertexShader = shader.vertexShader.replace( 'void', `
  48.  
  49. attribute vec2 scale;
  50. attribute float alpha;
  51.  
  52. varying float vAlpha;
  53.  
  54. void` ).replace( '<begin_vertex>', `<begin_vertex>
  55.  
  56. if ( scale.x != 0.0 && scale.y != 0.0 ) {
  57.  
  58. if ( transformed.x == 1.0 || transformed.x == 0.5 ) {
  59.  
  60. transformed.yz *= scale.x;
  61.  
  62. } else if ( transformed.x == - 1.0 || transformed.x == - 0.5 ) {
  63.  
  64. transformed.yz *= scale.y;
  65.  
  66. }
  67.  
  68. }
  69.  
  70. vAlpha = alpha;
  71.  
  72. ` );
  73.  
  74. shader.fragmentShader = shader.fragmentShader.replace( 'void', `
  75.  
  76. varying float vAlpha;
  77.  
  78. void` ).replace( '}', `
  79.  
  80. gl_FragColor.a *= vAlpha;
  81.  
  82. }` );
  83.  
  84. }
  85.  
  86. for ( let i = 0; i < materials.length; i ++ ) {
  87.  
  88. materials[ i ].onBeforeCompile = onBeforeCompile;
  89.  
  90. }
  91.  
  92. const instances = {};
  93.  
  94. const array = [ {
  95. name: 'sphere',
  96. geometry: new THREE.SphereGeometry( 1, 16 ),
  97. count: 150
  98. }, {
  99. name: 'cylinder',
  100. geometry: new THREE.CylinderGeometry( 0.5, 0.5, 1, 16 ).rotateZ( Math.PI / 2 ),
  101. count: 75,
  102. hasScaling: true
  103. }, {
  104. name: 'poly3',
  105. geometry: new THREE.CylinderGeometry( 1, 1, 1, 3, 1, false, - Math.PI / 6 ).rotateX( Math.PI / 2 ),
  106. count: 75
  107. }, {
  108. name: 'poly4',
  109. geometry: new THREE.BoxGeometry( 1, 1, 1 ),
  110. count: 75
  111. }, {
  112. name: 'poly5',
  113. geometry: new THREE.CylinderGeometry( 1, 1, 1, 5, 1, false, Math.PI / 10 ).rotateX( Math.PI / 2 ),
  114. count: 40
  115. }, {
  116. name: 'poly6',
  117. geometry: new THREE.CylinderGeometry( 1, 1, 1, 6, 1, false, - Math.PI / 12 ).rotateX( Math.PI / 2 ),
  118. count: 10
  119. } ];
  120.  
  121. for ( let i = 0; i < array.length; i ++ ) {
  122.  
  123. const { name, geometry, count, hasScaling } = array[ i ];
  124.  
  125. if ( hasScaling ) {
  126.  
  127. geometry.setAttribute( 'scale', new THREE.InstancedBufferAttribute( new Float32Array( count * 2 ), 2 ) );
  128.  
  129. }
  130.  
  131. geometry.setAttribute( 'alpha', new THREE.InstancedBufferAttribute( new Float32Array( count ), 1 ) );
  132.  
  133. const main = new THREE.InstancedMesh( geometry, materials[ materialIndex ], count );
  134. main.layers.set( MAIN_LAYER );
  135. scene.add( main );
  136.  
  137. const outline = new THREE.InstancedMesh( geometry, outlineMaterial, count );
  138. outline.layers.set( OUTLINE_LAYER );
  139. scene.add( outline );
  140.  
  141. main.setColorAt( 0, tempColor );
  142. outline.setColorAt( 0, tempColor );
  143.  
  144. instances[ name ] = {
  145. main,
  146. outline,
  147. count,
  148. hasScaling,
  149. index: 0
  150. };
  151.  
  152. }
  153.  
  154. const stack = [];
  155.  
  156. function getStack( index ) {
  157.  
  158. const result = stack[ stack.length - 1 - index ];
  159.  
  160. if ( result ) {
  161.  
  162. return result;
  163.  
  164. }
  165.  
  166. return { name: 'none' };
  167.  
  168. }
  169.  
  170. function setObject( name, x, y, z, sx, sy, sz, angle, color, alpha = 1, scaleX = 1, scaleY = 1 ) {
  171.  
  172. tempObject.position.set( x, y, z );
  173. tempObject.scale.set( sx, sy, sz );
  174. tempObject.rotation.set( 0, 0, angle );
  175.  
  176. tempObject.updateMatrix();
  177.  
  178. tempColor.set( color );
  179.  
  180. const instance = instances[ name ];
  181.  
  182. instance.main.setMatrixAt( instance.index, tempObject.matrix );
  183. instance.main.setColorAt( instance.index, tempColor );
  184.  
  185. instance.main.geometry.attributes.alpha.setX( instance.index, alpha );
  186. instance.outline.geometry.attributes.alpha.setX( instance.index, alpha );
  187.  
  188. const outlineSize = 4 / window.innerHeight * ( name === 'sphere' ? 0.7 : 1 );
  189.  
  190. if ( instance.hasScaling ) {
  191.  
  192. tempObject.scale.x += outlineSize;
  193. tempObject.scale.y += outlineSize / scaleY;
  194. tempObject.scale.z += outlineSize / scaleY;
  195.  
  196. } else {
  197.  
  198. tempObject.scale.addScalar( outlineSize );
  199.  
  200. }
  201.  
  202. tempObject.updateMatrix();
  203.  
  204. tempColor.multiplyScalar( 0.6 );
  205.  
  206. instance.outline.setMatrixAt( instance.index, tempObject.matrix );
  207. instance.outline.setColorAt( instance.index, tempColor );
  208.  
  209. if ( instance.hasScaling ) {
  210.  
  211. instance.main.geometry.attributes.scale.setXY( instance.index, scaleX, scaleY );
  212. instance.outline.geometry.attributes.scale.setXY( instance.index, scaleX, scaleY );
  213.  
  214. }
  215.  
  216. instance.index ++;
  217.  
  218. stack.push( { name, x, y, z, sx, sy, sz, angle, color, outlineSize, alpha } );
  219.  
  220. }
  221.  
  222. function init() {
  223.  
  224. window.addEventListener( 'DOMContentLoaded', function () {
  225. canvas = document.getElementById( 'canvas' );
  226.  
  227. renderer = new THREE.WebGLRenderer( {
  228. antialias: true,
  229. alpha: true
  230. } );
  231.  
  232. renderer.autoClear = false;
  233.  
  234. renderer.setPixelRatio( window.devicePixelRatio );
  235. renderer.setSize( canvas.width, canvas.height, false );
  236.  
  237. controls = new THREE.OrbitControls( camera, canvas );
  238.  
  239. controls.enabled = false;
  240.  
  241. window.addEventListener( 'resize', onWindowResize );
  242. } );
  243.  
  244. scene = new THREE.Scene();
  245.  
  246. camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.1, 1000 );
  247.  
  248. ortho = new THREE.OrthographicCamera( - camera.aspect / 2, camera.aspect / 2, 0.5, - 0.5, 0, 1000 );
  249.  
  250. currentCamera = camera;
  251.  
  252. const oldZ = Math.sin( Math.PI / 3 );
  253. camera.position.z = ortho.position.z = oldZ;
  254.  
  255. const ambLight = new THREE.AmbientLight( 0xffffff, 0.5 );
  256. ambLight.layers.set( MAIN_LAYER );
  257. scene.add( ambLight );
  258.  
  259. const dirLight = new THREE.DirectionalLight( 0xffffff, 0.5 );
  260. dirLight.layers.set( MAIN_LAYER );
  261. dirLight.position.z = 1;
  262. scene.add( dirLight );
  263.  
  264. window.addEventListener( 'keyup', function ( event ) {
  265.  
  266. const key = String.fromCharCode( event.keyCode );
  267.  
  268. if ( key === 'V' ) {
  269.  
  270. controls.enabled = ! controls.enabled;
  271.  
  272. if ( ! controls.enabled ) {
  273.  
  274. camera.position.set( 0, 0, oldZ );
  275. camera.rotation.set( 0, 0, 0 );
  276.  
  277. controls.target.set( 0, 0, 0 );
  278.  
  279. ortho.position.set( 0, 0, oldZ );
  280. ortho.rotation.set( 0, 0, 0 );
  281.  
  282. ortho.zoom = 1;
  283.  
  284. }
  285.  
  286. } else if ( key === 'P' ) {
  287.  
  288. currentCamera = currentCamera === camera ? ortho : camera;
  289.  
  290. currentCamera.position.copy( controls.object.position );
  291. currentCamera.rotation.copy( controls.object.rotation );
  292.  
  293. controls.object = currentCamera;
  294.  
  295. } else if ( key === 'B' ) {
  296.  
  297. materialIndex = ( materialIndex + 1 ) % materials.length;
  298.  
  299. for ( let key in instances ) {
  300.  
  301. instances[ key ].main.material = materials[ materialIndex ];
  302.  
  303. }
  304.  
  305. }
  306.  
  307. } );
  308.  
  309. }
  310.  
  311. function onWindowResize() {
  312.  
  313. renderer.setSize( canvas.width, canvas.height, false );
  314. camera.aspect = canvas.width / canvas.height;
  315. camera.updateProjectionMatrix();
  316.  
  317. ortho.left = - camera.aspect / 2;
  318. ortho.right = camera.aspect / 2;
  319. ortho.updateProjectionMatrix();
  320.  
  321. }
  322.  
  323. window.requestAnimationFrame = new Proxy( window.requestAnimationFrame, {
  324. apply( target, thisArgs, args ) {
  325.  
  326. args[ 0 ] = new Proxy( args[ 0 ], {
  327. apply( target, thisArgs, args ) {
  328.  
  329. stack.length = 0;
  330.  
  331. tempObject.position.setScalar( 0 );
  332. tempObject.scale.setScalar( 0 );
  333. tempObject.rotation.set( 0, 0, 0 );
  334.  
  335. tempObject.updateMatrix();
  336.  
  337. tempColor.setRGB( 0, 0, 0 );
  338.  
  339. for ( let key in instances ) {
  340.  
  341. const { main, outline, count, hasScaling } = instances[ key ];
  342.  
  343. for ( let i = 0; i < count; i ++ ) {
  344.  
  345. main.setMatrixAt( i, tempObject.matrix );
  346. outline.setMatrixAt( i, tempObject.matrix );
  347.  
  348. }
  349.  
  350. main.instanceMatrix.needsUpdate = true;
  351. main.instanceColor.needsUpdate = true;
  352.  
  353. outline.instanceMatrix.needsUpdate = true;
  354. outline.instanceColor.needsUpdate = true;
  355.  
  356. if ( hasScaling ) {
  357.  
  358. main.geometry.attributes.scale.needsUpdate = true;
  359. outline.geometry.attributes.scale.needsUpdate = true;
  360.  
  361. }
  362.  
  363. main.geometry.attributes.alpha.needsUpdate = true;
  364. outline.geometry.attributes.alpha.needsUpdate = true;
  365.  
  366. instances[ key ].index = 0;
  367.  
  368. }
  369.  
  370. arcCounter = 0;
  371. renderCounter = 0;
  372.  
  373. Reflect.apply( ...arguments );
  374.  
  375.  
  376. }
  377. } );
  378.  
  379. return Reflect.apply( ...arguments );
  380.  
  381. }
  382. } );
  383.  
  384. const Context2D = CanvasRenderingContext2D.prototype;
  385.  
  386. let arcCounter = 0;
  387.  
  388. Context2D.arc = new Proxy( Context2D.arc, {
  389. apply( target, thisArgs, args ) {
  390.  
  391. if ( args[ 4 ] === Math.PI * 2 ) {
  392.  
  393. if ( arcCounter === 0 ) {
  394.  
  395. const matrix = thisArgs.getTransform();
  396.  
  397. const r = matrix.a / canvas.height;
  398.  
  399. const x = ( matrix.e / canvas.width - 0.5 ) * camera.aspect;
  400. const y = 0.5 - matrix.f / canvas.height;
  401.  
  402. let z = 0;
  403.  
  404. const s0 = getStack( 0 );
  405. const s1 = getStack( 1 );
  406.  
  407. if ( s0.name === 'cylinder' && s1.name === 'sphere' && Math.hypot( x - s1.x, y - s1.y ) < 0.001 ) {
  408.  
  409. z = s1.sz;
  410.  
  411. const index = ( instances.cylinder.index - 1 ) * 16 + 14;
  412.  
  413. const newDepth = z + r - s0.sz / 2;
  414.  
  415. instances.cylinder.main.instanceMatrix.array[ index ] = newDepth;
  416. instances.cylinder.outline.instanceMatrix.array[ index ] = newDepth;
  417.  
  418. } else myBlock: {
  419.  
  420. if ( getStack( 0 ).name === 'cylinder' &&
  421. getStack( 1 ).name === 'sphere' &&
  422. getStack( 2 ).name === 'cylinder' &&
  423. getStack( 3 ).name === 'sphere' &&
  424. getStack( 4 ).name === 'cylinder' &&
  425. getStack( 5 ).name === 'poly3' &&
  426. getStack( 6 ).name === 'cylinder' ) {
  427.  
  428. z = getStack( 5 ).sz / 2;
  429.  
  430. const tr = getStack( 2 ).sz;
  431.  
  432. for ( let i = 0; i < 3; i ++ ) {
  433.  
  434. const index = ( instances.cylinder.index - 1 - i ) * 16 + 14;
  435.  
  436. const newDepth = z + r - tr / 2;
  437.  
  438. instances.cylinder.main.instanceMatrix.array[ index ] = newDepth;
  439. instances.cylinder.outline.instanceMatrix.array[ index ] = newDepth;
  440.  
  441. }
  442.  
  443. for ( let i = 0; i < 2; i ++ ) {
  444.  
  445. const index = ( instances.sphere.index - 1 - i ) * 16 + 14;
  446.  
  447. instances.sphere.main.instanceMatrix.array[ index ] = z;
  448. instances.sphere.outline.instanceMatrix.array[ index ] = z;
  449.  
  450. }
  451.  
  452. break myBlock;
  453.  
  454. }
  455.  
  456. for ( let i = 0; i < 5; i ++ ) {
  457.  
  458. if ( getStack( i ).name !== 'cylinder' ) {
  459.  
  460. break myBlock;
  461.  
  462. }
  463.  
  464. }
  465.  
  466. if ( getStack( 0 ).angle !== getStack( 2 ).angle ) {
  467.  
  468. break myBlock;
  469.  
  470. }
  471.  
  472. const a = r - getStack( 0 ).sy;
  473.  
  474. for ( let i = 0; i < 5; i ++ ) {
  475.  
  476. const index = ( instances.cylinder.index - 1 - i ) * 16 + 14;
  477.  
  478. const newDepth = a - a * 2 * i / 4;
  479.  
  480. instances.cylinder.main.instanceMatrix.array[ index ] = newDepth;
  481. instances.cylinder.outline.instanceMatrix.array[ index ] = newDepth;
  482.  
  483. }
  484.  
  485. }
  486.  
  487. checkIfIsMainCanvas( thisArgs, 'sphere' );
  488.  
  489. setObject(
  490. 'sphere',
  491. x,
  492. y,
  493. z,
  494. r,
  495. r,
  496. r,
  497. 0,
  498. thisArgs.fillStyle,
  499. thisArgs.globalAlpha
  500. );
  501.  
  502. } else if ( arcCounter === 1 ) {
  503.  
  504. tempColor.set( thisArgs.fillStyle );
  505. instances.sphere.main.setColorAt( instances.sphere.index - 1, tempColor );
  506.  
  507. tempColor.multiplyScalar( 0.6 );
  508. instances.sphere.outline.setColorAt( instances.sphere.index - 1, tempColor );
  509.  
  510. }
  511.  
  512. arcCounter = ( arcCounter + 1 ) % 3;
  513.  
  514. }
  515.  
  516. return Reflect.apply( ...arguments );
  517.  
  518. }
  519. } );
  520.  
  521. Context2D.rect = new Proxy( Context2D.rect, {
  522. apply( target, thisArgs, args ) {
  523.  
  524. const matrix = thisArgs.getTransform();
  525.  
  526. const isTurret = matrix.b !== 0 && matrix.c !== 0;
  527.  
  528. if ( isTurret || ( thisArgs.canvas === canvas && Math.hypot( matrix.c, matrix.d ) > 100 && thisArgs.globalAlpha === 1 ) ) {
  529.  
  530. const center = new DOMPoint( 0.5, 0.5 ).matrixTransform( matrix );
  531.  
  532. const scaleYZ = Math.hypot( matrix.c, matrix.d ) / canvas.height;
  533.  
  534. const name = isTurret ? 'cylinder' : 'poly4';
  535.  
  536. checkIfIsMainCanvas( thisArgs, name );
  537.  
  538. setObject(
  539. name,
  540. ( center.x / canvas.width - 0.5 ) * camera.aspect,
  541. 0.5 - center.y / canvas.height,
  542. isTurret ? 0 : 0.05,
  543. Math.hypot( matrix.a, matrix.b ) / canvas.height,
  544. scaleYZ,
  545. isTurret ? scaleYZ : 0.1,
  546. Math.atan2( matrix.c, matrix.d ),
  547. thisArgs.fillStyle,
  548. thisArgs.globalAlpha
  549. );
  550.  
  551. }
  552.  
  553. return Reflect.apply( ...arguments );
  554.  
  555. }
  556. } );
  557.  
  558. const points = [];
  559. let hasCurve = true;
  560.  
  561. Context2D.beginPath = new Proxy( Context2D.beginPath, {
  562. apply( target, thisArgs, args ) {
  563.  
  564. points.length = 0;
  565. hasCurve = false;
  566.  
  567. return Reflect.apply( ...arguments );
  568.  
  569. }
  570. } );
  571.  
  572. const addPoint = {
  573. apply( target, thisArgs, [ x, y ] ) {
  574.  
  575. points.push( new DOMPoint( x, y ).matrixTransform( thisArgs.getTransform() ) );
  576.  
  577. return Reflect.apply( ...arguments );
  578.  
  579. }
  580. };
  581.  
  582. Context2D.moveTo = new Proxy( Context2D.moveTo, addPoint );
  583. Context2D.lineTo = new Proxy( Context2D.lineTo, addPoint );
  584.  
  585. Context2D.arc = new Proxy( Context2D.arc, {
  586. apply( target, thisArgs, args ) {
  587.  
  588. hasCurve = true;
  589.  
  590. return Reflect.apply( ...arguments );
  591.  
  592. }
  593. } );
  594.  
  595. Context2D.fill = new Proxy( Context2D.fill, {
  596. apply( target, thisArgs, args ) {
  597.  
  598. if ( ! hasCurve ) {
  599.  
  600. if ( points.length > 2 && points.length < 7 ) myBlock: {
  601.  
  602. const center = { x: 0, y: 0 };
  603.  
  604. const count = points.length;
  605.  
  606. for ( let i = 0; i < count; i ++ ) {
  607.  
  608. center.x += points[ i ].x;
  609. center.y += points[ i ].y;
  610.  
  611. }
  612.  
  613. center.x /= count;
  614. center.y /= count;
  615.  
  616. if ( points.length === 6 ) {
  617.  
  618. const d1 = Math.hypot( points[ 0 ].x - center.x, points[ 0 ].y - center.y );
  619. const d2 = Math.hypot( points[ 1 ].x - center.x, points[ 1 ].y - center.y );
  620.  
  621. if ( Math.abs( d1 - d2 ) > 0.01 ) {
  622.  
  623. break myBlock;
  624.  
  625. }
  626.  
  627. }
  628.  
  629. let s, sx, angle, scaleX, scaleY;
  630.  
  631. let name = 'poly' + points.length;
  632.  
  633. if ( points.length === 4 ) {
  634.  
  635. const [ p0, p1, p2 ] = points;
  636. const pl = points[ points.length - 1 ];
  637.  
  638. scaleX = Math.hypot( p1.x - p2.x, p1.y - p2.y ) / canvas.height;
  639. scaleY = Math.hypot( p0.x - pl.x, p0.y - pl.y ) / canvas.height;
  640.  
  641. const dx = ( p1.x + p2.x ) / 2 - ( p0.x + pl.x ) / 2;
  642. const dy = ( p1.y + p2.y ) / 2 - ( p0.y + pl.y ) / 2;
  643.  
  644. sx = Math.hypot( dx, dy ) / canvas.height;
  645. angle = Math.atan2( dx, dy ) - Math.PI / 2;
  646.  
  647. if ( Math.abs( scaleX - scaleY ) > 0.001 ) {
  648.  
  649. s = 1;
  650. name = 'cylinder';
  651.  
  652. } else {
  653.  
  654. s = sx = scaleY;
  655.  
  656. }
  657.  
  658. } else {
  659.  
  660. s = sx = Math.hypot( points[ 0 ].x - center.x, points[ 0 ].y - center.y ) / canvas.height;
  661.  
  662. angle = - Math.atan2( points[ 0 ].y - center.y, points[ 0 ].x - center.x );
  663.  
  664. }
  665.  
  666. checkIfIsMainCanvas( thisArgs, name );
  667.  
  668. setObject(
  669. name,
  670. ( center.x / canvas.width - 0.5 ) * camera.aspect,
  671. 0.5 - center.y / canvas.height,
  672. 0,
  673. sx,
  674. s,
  675. s,
  676. angle,
  677. thisArgs.fillStyle,
  678. thisArgs.globalAlpha,
  679. scaleX,
  680. scaleY
  681. );
  682.  
  683. }
  684.  
  685. }
  686.  
  687. return Reflect.apply( ...arguments );
  688.  
  689. }
  690. } );
  691.  
  692. Context2D.fillText = new Proxy( Context2D.fillText, {
  693. apply( target, thisArgs, [ text ] ) {
  694.  
  695. if ( text === 'diep.io' ) {
  696.  
  697. thisArgs.canvas.isScoreboard = true;
  698.  
  699. }
  700.  
  701. return Reflect.apply( ...arguments );
  702.  
  703. }
  704. } );
  705.  
  706. let renderCounter = 0;
  707.  
  708. function render() {
  709.  
  710. renderCounter ++;
  711.  
  712. if ( renderCounter > 1 ) {
  713.  
  714. console.log( renderCounter, '!!!!' );
  715.  
  716. }
  717.  
  718. renderer.clear();
  719.  
  720. currentCamera.layers.set( OUTLINE_LAYER );
  721.  
  722. renderer.render( scene, currentCamera );
  723.  
  724. renderer.clearDepth();
  725.  
  726. currentCamera.layers.set( MAIN_LAYER );
  727.  
  728. renderer.render( scene, currentCamera );
  729.  
  730. }
  731.  
  732. function drawTexts( ctx ) {
  733.  
  734. const texts = [ 'Diep3D by Zert', 'Also try triep.io & hornex.pro' ];
  735.  
  736. const s = Math.min( ctx.canvas.width / 1200, ctx.canvas.height / 700 ) * 1.2;
  737. ctx.scale( s, s );
  738.  
  739. ctx.translate( 10, 10 );
  740.  
  741. ctx.font = 'bolder 11px Ubuntu';
  742. ctx.fillStyle = '#fff';
  743. ctx.strokeStyle = '#444';
  744. ctx.lineWidth = 2;
  745.  
  746. ctx.textBaseline = 'top';
  747. ctx.textAlign = 'left';
  748.  
  749. for ( let i = 0; i < texts.length; i ++ ) {
  750. const text = texts[ i ];
  751.  
  752. ctx.strokeText( text, 0, 0 );
  753. ctx.fillText( text, 0, 0 );
  754.  
  755. ctx.translate( 0, 14 );
  756.  
  757. }
  758.  
  759. }
  760.  
  761. Context2D.drawImage = new Proxy( Context2D.drawImage, {
  762. apply( target, thisArgs, args ) {
  763.  
  764. if ( args[ 0 ].isScoreboard && thisArgs.canvas.width === canvas.width && thisArgs.canvas.height === canvas.height ) {
  765.  
  766. thisArgs.canvas.hasScoreboard = true;
  767.  
  768. } else if ( args[ 0 ].hasScoreboard ) {
  769.  
  770. render();
  771.  
  772. thisArgs.save();
  773. thisArgs.globalAlpha = 1;
  774. thisArgs.setTransform( 1, 0, 0, 1, 0, 0 );
  775.  
  776. Reflect.apply( target, thisArgs, [ renderer.domElement, 0, 0, canvas.width, canvas.height ] );
  777.  
  778. drawTexts( thisArgs );
  779.  
  780. thisArgs.restore();
  781.  
  782. }
  783.  
  784. if ( thisArgs.canvas === canvas && args[ 0 ].objects ) {
  785.  
  786. const matrix = thisArgs.getTransform();
  787.  
  788. const x = matrix.e / canvas.width;
  789. const y = matrix.f / canvas.height;
  790.  
  791. const sx = Math.hypot( matrix.a, matrix.b );
  792. const sy = Math.hypot( matrix.c, matrix.d );
  793.  
  794. for ( let i = 0; i < args[ 0 ].objects.length; i ++ ) {
  795.  
  796. const { name, index } = args[ 0 ].objects[ i ];
  797.  
  798. const instance = instances[ name ];
  799.  
  800. const ma = instance.main.instanceMatrix.array;
  801. const oa = instance.outline.instanceMatrix.array;
  802.  
  803. const idx = index * 16;
  804.  
  805. const ox = ma[ idx + 12 ] / camera.aspect + 0.5;
  806. const oy = - ma[ idx + 13 ] + 0.5;
  807.  
  808. const outlineOldSx = Math.hypot( oa[ idx + 0 ], oa[ idx + 1 ] );
  809. const outlineOldSy = Math.hypot( oa[ idx + 4 ], oa[ idx + 5 ] );
  810.  
  811. const outlineSizeX = outlineOldSx - Math.hypot( ma[ idx + 0 ], ma[ idx + 1 ] );
  812. const outlineSizeY = outlineOldSy - Math.hypot( ma[ idx + 4 ], ma[ idx + 5 ] );
  813.  
  814. ma[ idx + 0 ] *= sx;
  815. ma[ idx + 1 ] *= sx;
  816. ma[ idx + 4 ] *= sy;
  817. ma[ idx + 5 ] *= sy;
  818. ma[ idx + 10 ] *= sy;
  819.  
  820. const nsx = Math.hypot( ma[ idx + 0 ], ma[ idx + 1 ] ) + outlineSizeX;
  821. const nsy = Math.hypot( ma[ idx + 4 ], ma[ idx + 5 ] ) + outlineSizeY;
  822.  
  823. oa[ idx + 0 ] *= nsx / outlineOldSx;
  824. oa[ idx + 1 ] *= nsx / outlineOldSx;
  825. oa[ idx + 4 ] *= nsy / outlineOldSy;
  826. oa[ idx + 5 ] *= nsy / outlineOldSy;
  827. oa[ idx + 10 ] *= sy;
  828.  
  829. ma[ idx + 12 ] = oa[ idx + 12 ] = ( ( ox * sx + x ) - 0.5 ) * camera.aspect;
  830. ma[ idx + 13 ] = oa[ idx + 13 ] = 0.5 - ( oy * sy + y );
  831.  
  832. instance.main.geometry.attributes.alpha.array[ index ] = thisArgs.globalAlpha;
  833. instance.outline.geometry.attributes.alpha.array[ index ] = thisArgs.globalAlpha;
  834.  
  835. }
  836.  
  837. delete args[ 0 ][ 'objects' ];
  838.  
  839. }
  840.  
  841. return Reflect.apply( ...arguments );
  842.  
  843. }
  844. } );
  845.  
  846. function checkIfIsMainCanvas( ctx, name ) {
  847.  
  848. if ( ctx.canvas !== canvas ) {
  849.  
  850. const { index } = instances[ name ];
  851.  
  852. if ( ctx.canvas.objects ) {
  853.  
  854. ctx.canvas.objects.push( { name, index } );
  855.  
  856. } else {
  857.  
  858. ctx.canvas.objects = [ { name, index } ];
  859.  
  860. }
  861.  
  862. }
  863.  
  864. }