diff --git a/extensions/extensions.json b/extensions/extensions.json
index c6892eb6d7..d49a2c7c67 100644
--- a/extensions/extensions.json
+++ b/extensions/extensions.json
@@ -19,6 +19,7 @@
"iframe",
"Xeltalliv/clippingblending",
"clipboard",
+ "obviousAlexC/penPlus",
"penplus",
"Lily/Skins",
"obviousAlexC/SensingPlus",
diff --git a/extensions/obviousAlexC/penPlus.js b/extensions/obviousAlexC/penPlus.js
new file mode 100644
index 0000000000..3d6c9fd469
--- /dev/null
+++ b/extensions/obviousAlexC/penPlus.js
@@ -0,0 +1,2704 @@
+// Name: Pen Plus V6
+// ID: penP
+// Description: Advanced rendering capabilities.
+// By: ObviousAlexC
+
+(function (Scratch) {
+ "use strict";
+
+ //?some smaller optimizations just store the multiplacation for later
+ const f32_4 = 4 * Float32Array.BYTES_PER_ELEMENT;
+ const f32_8 = 8 * Float32Array.BYTES_PER_ELEMENT;
+ const f32_10 = 10 * Float32Array.BYTES_PER_ELEMENT;
+ const d2r = 0.0174533;
+
+ //?Declare most of the main repo's we are going to use around the scratch vm
+ const vm = Scratch.vm;
+ const runtime = vm.runtime;
+ const renderer = runtime.renderer;
+ const shaderManager = renderer._shaderManager;
+
+ const canvas = renderer.canvas;
+ const gl = renderer._gl;
+ let currentFilter = gl.NEAREST;
+
+ let nativeSize = renderer.useHighQualityRender
+ ? [canvas.width, canvas.height]
+ : renderer._nativeSize;
+
+ //?create the depth buffer's texture
+ //*Create it in scratch's gl so that we have it stored in there!
+ let depthBufferTexture = gl.createTexture();
+
+ //?Make a function for updating the depth canvas to fit the scratch stage
+ const depthFrameBuffer = gl.createFramebuffer();
+ const depthColorBuffer = gl.createRenderbuffer();
+ const depthDepthBuffer = gl.createRenderbuffer();
+
+ let lastFB = gl.getParameter(gl.FRAMEBUFFER_BINDING);
+
+ //?Buffer handling and pen loading
+ {
+ gl.bindTexture(gl.TEXTURE_2D, depthBufferTexture);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
+ gl.texImage2D(
+ gl.TEXTURE_2D,
+ 0,
+ gl.RGBA,
+ nativeSize[0],
+ nativeSize[1],
+ 0,
+ gl.RGBA,
+ gl.UNSIGNED_BYTE,
+ null
+ );
+
+ gl.activeTexture(gl.TEXTURE1);
+ gl.bindTexture(gl.TEXTURE_2D, depthBufferTexture);
+ gl.activeTexture(gl.TEXTURE0);
+
+ gl.bindFramebuffer(gl.FRAMEBUFFER, depthFrameBuffer);
+
+ gl.bindRenderbuffer(gl.RENDERBUFFER, depthColorBuffer);
+ gl.renderbufferStorage(
+ gl.RENDERBUFFER,
+ gl.RGBA8 || gl.RGBA4,
+ nativeSize[0],
+ nativeSize[1]
+ );
+ gl.framebufferRenderbuffer(
+ gl.FRAMEBUFFER,
+ gl.COLOR_ATTACHMENT0,
+ gl.RENDERBUFFER,
+ depthColorBuffer
+ );
+
+ gl.bindRenderbuffer(gl.RENDERBUFFER, depthDepthBuffer);
+ gl.renderbufferStorage(
+ gl.RENDERBUFFER,
+ gl.DEPTH_COMPONENT16,
+ nativeSize[0],
+ nativeSize[1]
+ );
+ gl.framebufferRenderbuffer(
+ gl.FRAMEBUFFER,
+ gl.DEPTH_ATTACHMENT,
+ gl.RENDERBUFFER,
+ depthDepthBuffer
+ );
+
+ gl.framebufferTexture2D(
+ gl.FRAMEBUFFER,
+ gl.COLOR_ATTACHMENT0,
+ gl.TEXTURE_2D,
+ depthBufferTexture,
+ 0
+ );
+
+ gl.enable(gl.DEPTH_TEST);
+
+ gl.bindFramebuffer(gl.FRAMEBUFFER, lastFB);
+
+ const updateCanvasSize = () => {
+ nativeSize = renderer.useHighQualityRender
+ ? [canvas.width, canvas.height]
+ : renderer._nativeSize;
+
+ lastFB = gl.getParameter(gl.FRAMEBUFFER_BINDING);
+
+ gl.bindFramebuffer(gl.FRAMEBUFFER, depthFrameBuffer);
+
+ gl.bindRenderbuffer(gl.RENDERBUFFER, depthColorBuffer);
+ gl.renderbufferStorage(
+ gl.RENDERBUFFER,
+ gl.RGBA8 || gl.RGBA4,
+ nativeSize[0],
+ nativeSize[1]
+ );
+
+ gl.bindRenderbuffer(gl.RENDERBUFFER, depthDepthBuffer);
+ gl.renderbufferStorage(
+ gl.RENDERBUFFER,
+ gl.DEPTH_COMPONENT16,
+ nativeSize[0],
+ nativeSize[1]
+ );
+
+ gl.activeTexture(gl.TEXTURE1);
+
+ gl.texImage2D(
+ gl.TEXTURE_2D,
+ 0,
+ gl.RGBA,
+ nativeSize[0],
+ nativeSize[1],
+ 0,
+ gl.RGBA,
+ gl.UNSIGNED_BYTE,
+ null
+ );
+
+ gl.activeTexture(gl.TEXTURE0);
+
+ gl.bindFramebuffer(gl.FRAMEBUFFER, lastFB);
+ };
+
+ //?Call it to have it consistant
+ updateCanvasSize();
+
+ //?Call every frame because I don't know of a way to detect when the stage is resized
+
+ window.addEventListener("resize", updateCanvasSize);
+ vm.runtime.on("STAGE_SIZE_CHANGED", () => {
+ updateCanvasSize();
+ });
+
+ vm.runtime.on("BEFORE_EXECUTE", () => {
+ if (
+ (renderer.useHighQualityRender
+ ? [canvas.width, canvas.height]
+ : renderer._nativeSize) != nativeSize
+ ) {
+ nativeSize = renderer.useHighQualityRender
+ ? [canvas.width, canvas.height]
+ : renderer._nativeSize;
+ updateCanvasSize();
+ }
+ });
+
+ gl.enable(gl.DEPTH_TEST);
+ gl.depthFunc(gl.LEQUAL);
+
+ //?Make sure pen is loaded!
+ if (!Scratch.vm.extensionManager.isExtensionLoaded("pen")) {
+ runtime.extensionManager.loadExtensionIdSync("pen");
+ }
+ }
+
+ //?Ported from Pen+ version 5
+ //?Just a costume library for data uris
+ const penPlusCostumeLibrary = {};
+ let penPlusImportWrapMode = gl.CLAMP_TO_EDGE;
+
+ //?Debug for depth
+ penPlusCostumeLibrary["!Debug_Depth"] = depthBufferTexture;
+
+ const checkForPen = (util) => {
+ const curTarget = util.target;
+ const customState = curTarget["_customState"];
+ if (!customState["Scratch.pen"]) {
+ customState["Scratch.pen"] = {
+ penDown: false,
+ color: 66.66,
+ saturation: 100,
+ brightness: 100,
+ transparency: 0,
+ _shade: 50,
+ penAttributes: {
+ color4f: [0, 0, 1, 1],
+ diameter: 1,
+ },
+ };
+ }
+ };
+
+ //*Define PEN+ variables >:)
+ const triangleDefaultAttributes = [
+ // U V TINT R G B Z W transparency U V TINT R G B Z W transparency U V TINT R G B Z W transparency
+ 0,
+ 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1,
+ 1,
+ ];
+ const squareDefaultAttributes = [
+ // width* height* rotation u-mul u v-mul v r g b transparency
+ 1, 1, 90, 1, 0, 1, 0, 1, 1, 1, 1, 1,
+ ];
+
+ const triangleAttributesOfAllSprites = {}; //!it dawned on me I have to do this
+
+ const squareAttributesOfAllSprites = {}; //?Doing this for part 2
+
+ //?Get Shaders
+ const penPlusShaders = {
+ untextured: {
+ Shaders: {
+ vert: `
+ attribute highp vec4 a_position;
+ attribute highp vec4 a_color;
+ varying highp vec4 v_color;
+
+ varying highp float v_depth;
+
+ void main()
+ {
+ v_color = a_color;
+ v_depth = a_position.z;
+ gl_Position = a_position * vec4(a_position.w,a_position.w,0,1);
+ }
+ `,
+ frag: `
+ varying highp vec4 v_color;
+
+ uniform mediump vec2 u_res;
+ uniform sampler2D u_depthTexture;
+
+ varying highp float v_depth;
+
+ void main()
+ {
+ gl_FragColor = v_color;
+ highp vec4 v_depthPart = texture2D(u_depthTexture,gl_FragCoord.xy/u_res);
+ highp float v_depthcalc = v_depthPart.r + floor((v_depthPart.g + floor(v_depthPart.b * 100.0 )) * 100.0);
+
+ highp float v_inDepth = v_depth;
+
+ if (v_depth < 0.0 ) {
+ v_inDepth = 0.0;
+ }
+ if (v_depth > 10000.0 ) {
+ v_inDepth = 10000.0;
+ }
+
+ if (v_depthcalc < v_inDepth){
+ gl_FragColor.a = 0.0;
+ }
+
+ gl_FragColor.rgb *= gl_FragColor.a;
+ }
+ `,
+ },
+ ProgramInf: null,
+ },
+ textured: {
+ Shaders: {
+ vert: `
+ attribute highp vec4 a_position;
+ attribute highp vec4 a_color;
+ attribute highp vec2 a_texCoord;
+
+ varying highp vec4 v_color;
+ varying highp vec2 v_texCoord;
+
+ varying highp float v_depth;
+
+ void main()
+ {
+ v_color = a_color;
+ v_texCoord = a_texCoord;
+ v_depth = a_position.z;
+ gl_Position = a_position * vec4(a_position.w,a_position.w,0,1);
+ }
+ `,
+ frag: `
+ uniform sampler2D u_texture;
+
+ varying highp vec2 v_texCoord;
+ varying highp vec4 v_color;
+
+ uniform mediump vec2 u_res;
+ uniform sampler2D u_depthTexture;
+
+ varying highp float v_depth;
+
+ void main()
+ {
+ gl_FragColor = texture2D(u_texture, v_texCoord) * v_color;
+ highp vec4 v_depthPart = texture2D(u_depthTexture,gl_FragCoord.xy/u_res);
+ highp float v_depthcalc = v_depthPart.r + floor((v_depthPart.g + floor(v_depthPart.b * 100.0 )) * 100.0);
+
+ highp float v_inDepth = v_depth;
+
+ if (v_depth < 0.0 ) {
+ v_inDepth = 0.0;
+ }
+ if (v_depth > 10000.0 ) {
+ v_inDepth = 10000.0;
+ }
+
+ if (v_depthcalc < v_inDepth){
+ gl_FragColor.a = 0.0;
+ }
+
+ gl_FragColor.rgb *= gl_FragColor.a;
+
+ }
+ `,
+ },
+ ProgramInf: null,
+ },
+ depth: {
+ Shaders: {
+ vert: `
+ attribute highp vec4 a_position;
+
+ varying highp float v_depth;
+
+ void main()
+ {
+ v_depth = a_position.z;
+ gl_Position = a_position * vec4(a_position.w,a_position.w,a_position.w * 0.0001,1);
+ }
+ `,
+ frag: `
+ varying highp float v_depth;
+
+ void main()
+ {
+ if (v_depth >= 10000.0) {
+ gl_FragColor = vec4(1,1,1,1);
+ }
+ else {
+ highp float d_100 = floor(v_depth / 100.0);
+ gl_FragColor = vec4(
+ mod(v_depth,1.0),
+ mod( floor( v_depth - mod(v_depth,1.0) )/100.0,1.0),
+ mod( floor( d_100 - mod(d_100,1.0) )/100.0,1.0),
+ 1);
+ }
+ }
+ `,
+ },
+ ProgramInf: null,
+ },
+ pen: {
+ program: null,
+ },
+ createAndCompileShaders: (vert, frag) => {
+ //? compile vertex Shader
+ const vertShader = gl.createShader(gl.VERTEX_SHADER);
+ try {
+ gl.shaderSource(vertShader, vert.trim());
+ gl.compileShader(vertShader);
+ if (!gl.getShaderParameter(vertShader, gl.COMPILE_STATUS)) {
+ throw gl.getShaderInfoLog(vertShader);
+ }
+ } catch (error) {
+ console.error(error);
+ }
+
+ //? compile fragment Shader
+ const fragShader = gl.createShader(gl.FRAGMENT_SHADER);
+ try {
+ gl.shaderSource(fragShader, frag.trim());
+ gl.compileShader(fragShader);
+ if (!gl.getShaderParameter(fragShader, gl.COMPILE_STATUS)) {
+ throw gl.getShaderInfoLog(fragShader);
+ }
+ } catch (error) {
+ console.error(error);
+ }
+
+ //? compile program
+ const program = gl.createProgram();
+ try {
+ gl.attachShader(program, vertShader);
+ gl.attachShader(program, fragShader);
+ gl.linkProgram(program);
+ if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
+ throw gl.getProgramInfoLog(program);
+ }
+
+ gl.validateProgram(program);
+ if (!gl.getProgramParameter(program, gl.VALIDATE_STATUS)) {
+ throw gl.getProgramInfoLog(program);
+ }
+ } catch (error) {
+ console.error(error);
+ }
+
+ return {
+ program: program,
+ vert: vertShader,
+ frag: fragShader,
+ };
+ },
+ };
+
+ //? Create program info
+ {
+ penPlusShaders.untextured.ProgramInf =
+ penPlusShaders.createAndCompileShaders(
+ penPlusShaders.untextured.Shaders.vert,
+ penPlusShaders.untextured.Shaders.frag
+ );
+ penPlusShaders.textured.ProgramInf = penPlusShaders.createAndCompileShaders(
+ penPlusShaders.textured.Shaders.vert,
+ penPlusShaders.textured.Shaders.frag
+ );
+
+ penPlusShaders.depth.ProgramInf = penPlusShaders.createAndCompileShaders(
+ penPlusShaders.depth.Shaders.vert,
+ penPlusShaders.depth.Shaders.frag
+ );
+ }
+
+ //?Untextured
+ const a_position_Location_untext = gl.getAttribLocation(
+ penPlusShaders.untextured.ProgramInf.program,
+ "a_position"
+ );
+ const a_color_Location_untext = gl.getAttribLocation(
+ penPlusShaders.untextured.ProgramInf.program,
+ "a_color"
+ );
+
+ //?Textured
+ const a_position_Location_text = gl.getAttribLocation(
+ penPlusShaders.textured.ProgramInf.program,
+ "a_position"
+ );
+ const a_color_Location_text = gl.getAttribLocation(
+ penPlusShaders.textured.ProgramInf.program,
+ "a_color"
+ );
+ const a_textCoord_Location_text = gl.getAttribLocation(
+ penPlusShaders.textured.ProgramInf.program,
+ "a_texCoord"
+ );
+
+ //?Uniforms
+ const u_texture_Location_text = gl.getUniformLocation(
+ penPlusShaders.textured.ProgramInf.program,
+ "u_texture"
+ );
+
+ const u_depthTexture_Location_untext = gl.getUniformLocation(
+ penPlusShaders.untextured.ProgramInf.program,
+ "u_depthTexture"
+ );
+
+ const u_depthTexture_Location_text = gl.getUniformLocation(
+ penPlusShaders.textured.ProgramInf.program,
+ "u_depthTexture"
+ );
+
+ const u_res_Location_untext = gl.getUniformLocation(
+ penPlusShaders.untextured.ProgramInf.program,
+ "u_res"
+ );
+
+ const u_res_Location_text = gl.getUniformLocation(
+ penPlusShaders.textured.ProgramInf.program,
+ "u_res"
+ );
+
+ //?Depth
+ const a_position_Location_depth = gl.getAttribLocation(
+ penPlusShaders.depth.ProgramInf.program,
+ "a_position"
+ );
+
+ //?Enables Attributes
+ const vertexBuffer = gl.createBuffer();
+ const depthVertexBuffer = gl.createBuffer();
+ let vertexBufferData = null;
+
+ {
+ gl.enableVertexAttribArray(a_position_Location_untext);
+ gl.enableVertexAttribArray(a_color_Location_untext);
+ gl.enableVertexAttribArray(a_position_Location_text);
+ gl.enableVertexAttribArray(a_color_Location_text);
+ gl.enableVertexAttribArray(a_textCoord_Location_text);
+ gl.enableVertexAttribArray(a_position_Location_depth);
+ gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
+ gl.bindBuffer(gl.ARRAY_BUFFER, null);
+ gl.bindBuffer(gl.ARRAY_BUFFER, depthVertexBuffer);
+ gl.bindBuffer(gl.ARRAY_BUFFER, null);
+ }
+
+ //?Override pen Clear with pen+
+ renderer.penClear = (penSkinID) => {
+ const lastCC = gl.getParameter(gl.COLOR_CLEAR_VALUE);
+ lastFB = gl.getParameter(gl.FRAMEBUFFER_BINDING);
+ //Pen+ Overrides default pen Clearing
+ gl.bindFramebuffer(gl.FRAMEBUFFER, depthFrameBuffer);
+ gl.clearColor(1, 1, 1, 1);
+ gl.clear(gl.DEPTH_BUFFER_BIT);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+
+ gl.bindFramebuffer(gl.FRAMEBUFFER, lastFB);
+ gl.clearColor(lastCC[0], lastCC[1], lastCC[2], lastCC[3]);
+
+ //? ^ just clear the depth buffer for when its added.
+
+ //Old clearing
+ renderer.dirty = true;
+ const skin = /** @type {PenSkin} */ renderer._allSkins[penSkinID];
+ skin.clear();
+ };
+
+ //Pen+ advanced options update
+ //I plan to add more later
+ const penPlusAdvancedSettings = {
+ wValueUnderFlow: false,
+ useDepthBuffer: true,
+ _ClampZ: false,
+ _maxDepth: 1000,
+ };
+
+ //?Have this here for ez pz tri drawing on the canvas
+ const triFunctions = {
+ drawTri: (curProgram, x1, y1, x2, y2, x3, y3, penColor, targetID) => {
+ //? get triangle attributes for current sprite.
+ const triAttribs = triangleAttributesOfAllSprites[targetID];
+
+ if (triAttribs) {
+ vertexBufferData = new Float32Array([
+ x1,
+ -y1,
+ triAttribs[5],
+ triAttribs[6],
+ penColor[0] * triAttribs[2],
+ penColor[1] * triAttribs[3],
+ penColor[2] * triAttribs[4],
+ penColor[3] * triAttribs[7],
+
+ x2,
+ -y2,
+ triAttribs[13],
+ triAttribs[14],
+ penColor[0] * triAttribs[10],
+ penColor[1] * triAttribs[11],
+ penColor[2] * triAttribs[12],
+ penColor[3] * triAttribs[15],
+
+ x3,
+ -y3,
+ triAttribs[21],
+ triAttribs[22],
+ penColor[0] * triAttribs[18],
+ penColor[1] * triAttribs[19],
+ penColor[2] * triAttribs[20],
+ penColor[3] * triAttribs[23],
+ ]);
+ } else {
+ vertexBufferData = new Float32Array([
+ x1,
+ -y1,
+ 1,
+ 1,
+ penColor[0],
+ penColor[1],
+ penColor[2],
+ penColor[3],
+
+ x2,
+ -y2,
+ 1,
+ 1,
+ penColor[0],
+ penColor[1],
+ penColor[2],
+ penColor[3],
+
+ x3,
+ -y3,
+ 1,
+ 1,
+ penColor[0],
+ penColor[1],
+ penColor[2],
+ penColor[3],
+ ]);
+ }
+
+ //? Bind Positional Data
+
+ gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
+ gl.bufferData(gl.ARRAY_BUFFER, vertexBufferData, gl.STATIC_DRAW);
+ gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
+
+ gl.vertexAttribPointer(
+ a_position_Location_untext,
+ 4,
+ gl.FLOAT,
+ false,
+ f32_8,
+ 0
+ );
+ gl.vertexAttribPointer(
+ a_color_Location_untext,
+ 4,
+ gl.FLOAT,
+ false,
+ f32_8,
+ f32_4
+ );
+
+ gl.useProgram(penPlusShaders.untextured.ProgramInf.program);
+
+ gl.uniform1i(u_depthTexture_Location_untext, 1);
+
+ gl.uniform2fv(u_res_Location_untext, nativeSize);
+
+ gl.drawArrays(gl.TRIANGLES, 0, 3);
+ //? Hacky fix but it works.
+
+ if (penPlusAdvancedSettings.useDepthBuffer) {
+ triFunctions.drawDepthTri(targetID, x1, y1, x2, y2, x3, y3);
+ }
+ gl.useProgram(penPlusShaders.pen.program);
+ },
+
+ drawTextTri: (curProgram, x1, y1, x2, y2, x3, y3, targetID, texture) => {
+ //? get triangle attributes for current sprite.
+ const triAttribs = triangleAttributesOfAllSprites[targetID];
+
+ if (triAttribs) {
+ vertexBufferData = new Float32Array([
+ x1,
+ -y1,
+ penPlusAdvancedSettings.useDepthBuffer ? triAttribs[5] : 0,
+ triAttribs[6],
+ triAttribs[2],
+ triAttribs[3],
+ triAttribs[4],
+ triAttribs[7],
+ triAttribs[0],
+ triAttribs[1],
+
+ x2,
+ -y2,
+ penPlusAdvancedSettings.useDepthBuffer ? triAttribs[13] : 0,
+ triAttribs[14],
+ triAttribs[10],
+ triAttribs[11],
+ triAttribs[12],
+ triAttribs[15],
+ triAttribs[8],
+ triAttribs[9],
+
+ x3,
+ -y3,
+ penPlusAdvancedSettings.useDepthBuffer ? triAttribs[21] : 0,
+ triAttribs[22],
+ triAttribs[18],
+ triAttribs[19],
+ triAttribs[20],
+ triAttribs[23],
+ triAttribs[16],
+ triAttribs[17],
+ ]);
+ } else {
+ vertexBufferData = new Float32Array([
+ x1,
+ -y1,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+
+ x2,
+ -y2,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+
+ x3,
+ -y3,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ ]);
+ }
+ //? Bind Positional Data
+ gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
+ gl.bufferData(gl.ARRAY_BUFFER, vertexBufferData, gl.DYNAMIC_DRAW);
+
+ gl.vertexAttribPointer(
+ a_position_Location_text,
+ 4,
+ gl.FLOAT,
+ false,
+ f32_10,
+ 0
+ );
+ gl.vertexAttribPointer(
+ a_color_Location_text,
+ 4,
+ gl.FLOAT,
+ false,
+ f32_10,
+ f32_4
+ );
+ gl.vertexAttribPointer(
+ a_textCoord_Location_text,
+ 2,
+ gl.FLOAT,
+ false,
+ f32_10,
+ f32_8
+ );
+
+ gl.useProgram(penPlusShaders.textured.ProgramInf.program);
+
+ gl.bindTexture(gl.TEXTURE_2D, texture);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, currentFilter);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, currentFilter);
+ gl.uniform1i(u_texture_Location_text, 0);
+
+ gl.uniform1i(u_depthTexture_Location_text, 1);
+
+ gl.uniform2fv(u_res_Location_text, nativeSize);
+
+ gl.drawArrays(gl.TRIANGLES, 0, 3);
+ if (penPlusAdvancedSettings.useDepthBuffer) {
+ triFunctions.drawDepthTri(targetID, x1, y1, x2, y2, x3, y3);
+ }
+ gl.useProgram(penPlusShaders.pen.program);
+ },
+
+ //? this is so I don't have to go through the hassle of replacing default scratch shaders
+ //? many of curse words where exchanged between me and a pillow while writing this extension
+ //? but I have previaled!
+ drawDepthTri: (targetID, x1, y1, x2, y2, x3, y3) => {
+ lastFB = gl.getParameter(gl.FRAMEBUFFER_BINDING);
+ const triAttribs = triangleAttributesOfAllSprites[targetID];
+ gl.bindFramebuffer(gl.FRAMEBUFFER, depthFrameBuffer);
+
+ if (triAttribs) {
+ vertexBufferData = new Float32Array([
+ x1,
+ -y1,
+ triAttribs[5],
+ triAttribs[6],
+
+ x2,
+ -y2,
+ triAttribs[13],
+ triAttribs[14],
+
+ x3,
+ -y3,
+ triAttribs[21],
+ triAttribs[22],
+ ]);
+ } else {
+ vertexBufferData = new Float32Array([
+ x1,
+ -y1,
+ 0,
+ 1,
+
+ x2,
+ -y2,
+ 0,
+ 1,
+
+ x3,
+ -y3,
+ 0,
+ 1,
+ ]);
+ }
+
+ //? Bind Positional Data
+ gl.bindBuffer(gl.ARRAY_BUFFER, depthVertexBuffer);
+ gl.bufferData(gl.ARRAY_BUFFER, vertexBufferData, gl.DYNAMIC_DRAW);
+
+ gl.vertexAttribPointer(
+ a_position_Location_depth,
+ 4,
+ gl.FLOAT,
+ false,
+ f32_4,
+ 0
+ );
+
+ gl.useProgram(penPlusShaders.depth.ProgramInf.program);
+
+ gl.drawArrays(gl.TRIANGLES, 0, 3);
+
+ gl.bindFramebuffer(gl.FRAMEBUFFER, lastFB);
+ },
+
+ setValueAccordingToCaseTriangle: (
+ targetId,
+ attribute,
+ value,
+ wholeTri,
+ offset
+ ) => {
+ offset = offset + attribute || attribute;
+ let valuetoSet = 0;
+ switch (attribute) {
+ //U
+ case 0:
+ valuetoSet = value;
+ break;
+ //V
+ case 1:
+ valuetoSet = value;
+ break;
+
+ //100 since that is what scratch users are accustomed to.
+ //R
+ case 2:
+ valuetoSet = Math.min(Math.max(value, 0), 100) * 0.01;
+ break;
+ //G
+ case 3:
+ valuetoSet = Math.min(Math.max(value, 0), 100) * 0.01;
+ break;
+ //B
+ case 4:
+ valuetoSet = Math.min(Math.max(value, 0), 100) * 0.01;
+ break;
+
+ //Clamp to 0 so we can't go behind the stage.
+ //Z
+ case 5:
+ if (penPlusAdvancedSettings._ClampZ) {
+ if (value < 0) {
+ valuetoSet = 0;
+ break;
+ }
+ //convert to depth space for best accuracy
+ valuetoSet = Math.min(
+ (value * 10000) / penPlusAdvancedSettings._maxDepth,
+ 10000
+ );
+ break;
+ }
+ //convert to depth space for best accuracy
+ valuetoSet = (value * 10000) / penPlusAdvancedSettings._maxDepth;
+ break;
+
+ //Clamp to 1 so we don't accidentally clip.
+ //W
+ case 6:
+ if (penPlusAdvancedSettings.wValueUnderFlow == true) {
+ valuetoSet = value;
+ } else {
+ valuetoSet = Math.max(value, 1);
+ }
+ break;
+ //Transparency
+ //Same story as color
+ case 7:
+ valuetoSet = Math.min(Math.max(value, 0), 1000) * 0.01;
+ break;
+
+ //Just break if value isn't valid
+ default:
+ break;
+ }
+ //Check if the index even exists.
+ if (attribute >= 0 && attribute <= 7) {
+ if (wholeTri) {
+ triangleAttributesOfAllSprites[targetId][attribute] = valuetoSet;
+ triangleAttributesOfAllSprites[targetId][attribute + 8] = valuetoSet;
+ triangleAttributesOfAllSprites[targetId][attribute + 16] = valuetoSet;
+ } else {
+ triangleAttributesOfAllSprites[targetId][offset] = valuetoSet;
+ }
+ }
+ },
+ };
+
+ const lilPenDabble = (InativeSize, curTarget, util) => {
+ checkForPen(util);
+
+ const attrib = curTarget["_customState"]["Scratch.pen"].penAttributes;
+
+ Scratch.vm.renderer.penLine(
+ Scratch.vm.renderer._penSkinId,
+ {
+ color4f: [1, 1, 1, 0.011],
+ diameter: 1,
+ },
+ InativeSize[0] / 2,
+ InativeSize[1] / 2,
+ InativeSize[0] / 2,
+ InativeSize[1] / 2
+ );
+ };
+
+ //?Color Library
+ const colors = {
+ hexToRgb: (hex) => {
+ if (typeof hex == "string") {
+ const splitHex = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
+ return {
+ r: parseInt(splitHex[1], 16),
+ g: parseInt(splitHex[2], 16),
+ b: parseInt(splitHex[3], 16),
+ };
+ }
+ hex = Scratch.Cast.toNumber(hex);
+ return {
+ r: Math.floor(hex / 65536),
+ g: Math.floor(hex / 256) % 256,
+ b: hex % 256,
+ };
+ },
+
+ rgbtoSColor: ({ R, G, B }) => {
+ R = Math.min(Math.max(R, 0), 100) * 2.55;
+ G = Math.min(Math.max(G, 0), 100) * 2.55;
+ B = Math.min(Math.max(B, 0), 100) * 2.55;
+ return (Math.ceil(R) * 256 + Math.ceil(G)) * 256 + Math.ceil(B);
+ },
+ };
+
+ const textureFunctions = {
+ createBlankPenPlusTextureInfo: function (
+ width,
+ height,
+ color,
+ name,
+ clamp
+ ) {
+ const texture = penPlusCostumeLibrary[name]
+ ? penPlusCostumeLibrary[name].texture
+ : gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, texture);
+ // Fill the texture with a 1x1 blue pixel.
+
+ const pixelData = new Uint8Array(width * height * 4);
+
+ const decodedColor = colors.hexToRgb(color);
+
+ for (let pixelID = 0; pixelID < pixelData.length / 4; pixelID++) {
+ pixelData[pixelID * 4] = decodedColor.r;
+ pixelData[pixelID * 4 + 1] = decodedColor.g;
+ pixelData[pixelID * 4 + 2] = decodedColor.b;
+ pixelData[pixelID * 4 + 3] = 255;
+ }
+
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, clamp);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, clamp);
+
+ gl.texImage2D(
+ gl.TEXTURE_2D,
+ 0,
+ gl.RGBA,
+ width,
+ height,
+ 0,
+ gl.RGBA,
+ gl.UNSIGNED_BYTE,
+ pixelData
+ );
+
+ penPlusCostumeLibrary[name] = {
+ texture: texture,
+ width: width,
+ height: height,
+ };
+ },
+ createPenPlusTextureInfo: function (url, name, clamp) {
+ const texture = penPlusCostumeLibrary[name]
+ ? penPlusCostumeLibrary[name].texture
+ : gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, texture);
+ // Fill the texture with a 1x1 blue pixel.
+ gl.texImage2D(
+ gl.TEXTURE_2D,
+ 0,
+ gl.RGBA,
+ 1,
+ 1,
+ 0,
+ gl.RGBA,
+ gl.UNSIGNED_BYTE,
+ new Uint8Array([0, 0, 255, 255])
+ );
+
+ // Let's assume all images are not a power of 2
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, clamp);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, clamp);
+ return new Promise((resolve, reject) => {
+ Scratch.canFetch(url).then((allowed) => {
+ if (!allowed) {
+ reject(false);
+ }
+ // Permission is checked earlier.
+ // eslint-disable-next-line no-restricted-syntax
+ const image = new Image();
+ image.onload = function () {
+ gl.bindTexture(gl.TEXTURE_2D, texture);
+ gl.texImage2D(
+ gl.TEXTURE_2D,
+ 0,
+ gl.RGBA,
+ gl.RGBA,
+ gl.UNSIGNED_BYTE,
+ image
+ );
+ penPlusCostumeLibrary[name] = {
+ texture: texture,
+ width: image.width,
+ height: image.height,
+ };
+ resolve(texture);
+ };
+ image.crossOrigin = "anonymous";
+ image.src = url;
+ });
+ });
+ },
+
+ getTextureData: (texture, width, height) => {
+ //?Initilize the temp framebuffer and assign it
+ const readBuffer = gl.createFramebuffer();
+
+ lastFB = gl.getParameter(gl.FRAMEBUFFER_BINDING);
+
+ gl.bindFramebuffer(gl.FRAMEBUFFER, readBuffer);
+
+ gl.framebufferTexture2D(
+ gl.FRAMEBUFFER,
+ gl.COLOR_ATTACHMENT0,
+ gl.TEXTURE_2D,
+ texture,
+ 0
+ );
+
+ //?make sure to unbind the framebuffer and delete it!
+ const removeBuffer = () => {
+ gl.deleteFramebuffer(readBuffer);
+ };
+
+ //?if sucessful read
+ if (
+ gl.checkFramebufferStatus(gl.FRAMEBUFFER) == gl.FRAMEBUFFER_COMPLETE
+ ) {
+ //?Make an array to write the pixels onto
+ let dataArray = new Uint8Array(width * height * 4);
+ gl.readPixels(
+ 0,
+ 0,
+ width,
+ height,
+ gl.RGBA,
+ gl.UNSIGNED_BYTE,
+ dataArray
+ );
+
+ //?Remove Buffer data and return data
+ removeBuffer();
+ return dataArray;
+ }
+
+ //?If not return undefined
+ removeBuffer();
+ return undefined;
+ },
+
+ getTextureAsURI: (texture, width, height) => {
+ //?Initilize the temp framebuffer and assign it
+ const readBuffer = gl.createFramebuffer();
+
+ lastFB = gl.getParameter(gl.FRAMEBUFFER_BINDING);
+
+ gl.bindFramebuffer(gl.FRAMEBUFFER, readBuffer);
+
+ gl.framebufferTexture2D(
+ gl.FRAMEBUFFER,
+ gl.COLOR_ATTACHMENT0,
+ gl.TEXTURE_2D,
+ texture,
+ 0
+ );
+
+ //?make sure to unbind the framebuffer and delete it!
+ const removeBuffer = () => {
+ gl.deleteFramebuffer(readBuffer);
+ };
+
+ //?if sucessful read
+ if (
+ gl.checkFramebufferStatus(gl.FRAMEBUFFER) == gl.FRAMEBUFFER_COMPLETE
+ ) {
+ //?Make an array to write the pixels onto
+ let dataArray = new Uint8Array(width * height * 4);
+ gl.readPixels(
+ 0,
+ 0,
+ width,
+ height,
+ gl.RGBA,
+ gl.UNSIGNED_BYTE,
+ dataArray
+ );
+
+ //Make an invisible canvas
+ const dataURICanvas = document.createElement("canvas");
+ dataURICanvas.width = width;
+ dataURICanvas.height = height;
+ const dataURIContext = dataURICanvas.getContext("2d");
+
+ // Copy the pixels to a 2D canvas
+ const imageData = dataURIContext.createImageData(width, height);
+ imageData.data.set(dataArray);
+ dataURIContext.putImageData(imageData, 0, 0);
+
+ //?Remove Buffer data and return data
+ removeBuffer();
+ return dataURICanvas.toDataURL();
+ }
+
+ //?If not return undefined
+ removeBuffer();
+ return undefined;
+ },
+ };
+
+ class extension {
+ getInfo() {
+ return {
+ blocks: [
+ {
+ opcode: "__NOUSEOPCODE",
+ blockType: Scratch.BlockType.LABEL,
+ text: "Pen Properties",
+ },
+ {
+ disableMonitor: true,
+ opcode: "isPenDown",
+ blockType: Scratch.BlockType.BOOLEAN,
+ text: "pen is down?",
+ arguments: {},
+ filter: "sprite",
+ },
+ {
+ disableMonitor: true,
+ opcode: "getPenHSV",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "pen [HSV]",
+ arguments: {
+ HSV: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "color",
+ menu: "hsvMenu",
+ },
+ },
+ filter: "sprite",
+ },
+ {
+ disableMonitor: true,
+ opcode: "drawDot",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "draw dot at [x] [y]",
+ arguments: {
+ x: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 },
+ y: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 },
+ },
+ filter: "sprite",
+ },
+ {
+ disableMonitor: true,
+ opcode: "drawLine",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "draw line from [x1] [y1] to [x2] [y2]",
+ arguments: {
+ x1: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 },
+ y1: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 },
+ x2: { type: Scratch.ArgumentType.NUMBER, defaultValue: 10 },
+ y2: { type: Scratch.ArgumentType.NUMBER, defaultValue: 10 },
+ },
+ filter: "sprite",
+ },
+ {
+ opcode: "__NOUSEOPCODE",
+ blockType: Scratch.BlockType.LABEL,
+ text: "Square Pen Blocks",
+ },
+ {
+ disableMonitor: true,
+ opcode: "squareDown",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "stamp pen square",
+ arguments: {},
+ filter: "sprite",
+ },
+ {
+ disableMonitor: true,
+ opcode: "squareTexDown",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "stamp pen square with the texture of [tex]",
+ arguments: {
+ tex: { type: Scratch.ArgumentType.STRING, menu: "costumeMenu" },
+ },
+ filter: "sprite",
+ },
+ {
+ disableMonitor: true,
+ opcode: "setStampAttribute",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set pen square's [target] to [number]",
+ arguments: {
+ target: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 0,
+ menu: "stampSquare",
+ },
+ number: { type: Scratch.ArgumentType.NUMBER, defaultValue: 1 },
+ },
+ filter: "sprite",
+ },
+ {
+ disableMonitor: true,
+ opcode: "getStampAttribute",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get pen square's [target]",
+ arguments: {
+ target: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 0,
+ menu: "stampSquare",
+ },
+ },
+ filter: "sprite",
+ },
+ {
+ disableMonitor: true,
+ opcode: "tintSquare",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "tint pen square to [color]",
+ arguments: {
+ color: {
+ type: Scratch.ArgumentType.COLOR,
+ defaultValue: "#0000ff",
+ },
+ },
+ filter: "sprite",
+ },
+ {
+ disableMonitor: true,
+ opcode: "resetSquareAttributes",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "reset square Attributes",
+ arguments: {},
+ filter: "sprite",
+ },
+ {
+ opcode: "__NOUSEOPCODE",
+ blockType: Scratch.BlockType.LABEL,
+ text: "Triangle Blocks",
+ },
+ {
+ disableMonitor: true,
+ opcode: "setTriangleFilterMode",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set triangle filter mode to [filter]",
+ arguments: {
+ filter: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 9728,
+ menu: "filterType",
+ },
+ },
+ filter: "sprite",
+ },
+ {
+ disableMonitor: true,
+ opcode: "setTrianglePointAttribute",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set triangle point [point]'s [attribute] to [value]",
+ arguments: {
+ point: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "1",
+ menu: "pointMenu",
+ },
+ attribute: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: "2",
+ menu: "triAttribute",
+ },
+ value: { type: Scratch.ArgumentType.NUMBER, defaultValue: 1 },
+ },
+ filter: "sprite",
+ },
+ {
+ disableMonitor: true,
+ opcode: "setWholeTrianglePointAttribute",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set triangle's [wholeAttribute] to [value]",
+ arguments: {
+ wholeAttribute: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: "2",
+ menu: "wholeTriAttribute",
+ },
+ value: { type: Scratch.ArgumentType.NUMBER, defaultValue: 1 },
+ },
+ filter: "sprite",
+ },
+ {
+ disableMonitor: true,
+ opcode: "tintTriPoint",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "tint triangle point [point] to [color]",
+ arguments: {
+ point: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "1",
+ menu: "pointMenu",
+ },
+ color: {
+ type: Scratch.ArgumentType.COLOR,
+ defaultValue: "#0000ff",
+ },
+ },
+ filter: "sprite",
+ },
+ {
+ disableMonitor: true,
+ opcode: "tintTri",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "tint triangle to [color]",
+ arguments: {
+ point: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "1",
+ menu: "pointMenu",
+ },
+ color: {
+ type: Scratch.ArgumentType.COLOR,
+ defaultValue: "#0000ff",
+ },
+ },
+ filter: "sprite",
+ },
+ {
+ disableMonitor: true,
+ opcode: "getTrianglePointAttribute",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get triangle point [point]'s [attribute]",
+ arguments: {
+ point: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "1",
+ menu: "pointMenu",
+ },
+ attribute: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 2,
+ menu: "triAttribute",
+ },
+ },
+ filter: "sprite",
+ },
+ {
+ disableMonitor: true,
+ opcode: "resetWholeTriangleAttributes",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "reset triangle attributes",
+ arguments: {},
+ filter: "sprite",
+ },
+ {
+ disableMonitor: true,
+ opcode: "drawSolidTri",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "draw triangle between [x1] [y1], [x2] [y2] and [x3] [y3]",
+ arguments: {
+ x1: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 },
+ y1: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 },
+ x2: { type: Scratch.ArgumentType.NUMBER, defaultValue: 10 },
+ y2: { type: Scratch.ArgumentType.NUMBER, defaultValue: 10 },
+ x3: { type: Scratch.ArgumentType.NUMBER, defaultValue: 10 },
+ y3: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 },
+ },
+ filter: "sprite",
+ },
+ {
+ disableMonitor: true,
+ opcode: "drawTexTri",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "draw textured triangle between [x1] [y1], [x2] [y2] and [x3] [y3] with the texture [tex]",
+ arguments: {
+ x1: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 },
+ y1: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 },
+ x2: { type: Scratch.ArgumentType.NUMBER, defaultValue: 10 },
+ y2: { type: Scratch.ArgumentType.NUMBER, defaultValue: 10 },
+ x3: { type: Scratch.ArgumentType.NUMBER, defaultValue: 10 },
+ y3: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 },
+ tex: { type: Scratch.ArgumentType.STRING, menu: "costumeMenu" },
+ },
+ filter: "sprite",
+ },
+ {
+ opcode: "__NOUSEOPCODE",
+ blockType: Scratch.BlockType.LABEL,
+ text: "Color",
+ },
+ {
+ disableMonitor: true,
+ opcode: "RGB2HEX",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "red [R] green [G] blue [B]",
+ arguments: {
+ R: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 },
+ G: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 },
+ B: { type: Scratch.ArgumentType.NUMBER, defaultValue: 100 },
+ },
+ },
+ {
+ disableMonitor: true,
+ opcode: "HSV2RGB",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "hue [H] saturation [S] value [V]",
+ arguments: {
+ H: { type: Scratch.ArgumentType.NUMBER, defaultValue: 0 },
+ S: { type: Scratch.ArgumentType.NUMBER, defaultValue: 100 },
+ V: { type: Scratch.ArgumentType.NUMBER, defaultValue: 100 },
+ },
+ },
+ {
+ opcode: "__NOUSEOPCODE",
+ blockType: Scratch.BlockType.LABEL,
+ text: "Images",
+ },
+ {
+ disableMonitor: true,
+ opcode: "setDURIclampmode",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set imported image wrap mode to [clampMode]",
+ arguments: {
+ clampMode: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "33071",
+ menu: "wrapType",
+ },
+ },
+ },
+ {
+ disableMonitor: true,
+ opcode: "addBlankIMG",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "add blank image that is [color] and the size of [width], [height] named [name] to Pen+ Library",
+ arguments: {
+ color: {
+ type: Scratch.ArgumentType.COLOR,
+ defaultValue: "#ffffff",
+ },
+ width: { type: Scratch.ArgumentType.NUMBER, defaultValue: 128 },
+ height: { type: Scratch.ArgumentType.NUMBER, defaultValue: 128 },
+ name: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "Image",
+ },
+ },
+ },
+ {
+ disableMonitor: true,
+ opcode: "addIMGfromDURI",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "add image named [name] from [dataURI] to Pen+ Library",
+ arguments: {
+ dataURI: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "https://extensions.turbowarp.org/dango.png",
+ },
+ name: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "Image",
+ },
+ },
+ },
+ {
+ disableMonitor: true,
+ opcode: "removeIMGfromDURI",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "remove image named [name] from Pen+ Library",
+ arguments: {
+ name: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "Image",
+ },
+ },
+ filter: "sprite",
+ },
+ {
+ disableMonitor: true,
+ opcode: "doesIMGexist",
+ blockType: Scratch.BlockType.BOOLEAN,
+ text: "does [name] exist in Pen+ Library",
+ arguments: {
+ name: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "Image",
+ },
+ },
+ filter: "sprite",
+ },
+ {
+ disableMonitor: true,
+ opcode: "getCostumeDataURI",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get data uri for costume [costume]",
+ arguments: {
+ costume: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "getCostumeDataURI_costume_Menu",
+ },
+ },
+ filter: "sprite",
+ },
+ {
+ disableMonitor: true,
+ opcode: "getDimensionOf",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get the [dimension] of [costume] in pen+ costume library",
+ arguments: {
+ dimension: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "getDimensionOf_dimension_Menu",
+ },
+ costume: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "penPlusCostumes",
+ },
+ },
+ filter: "sprite",
+ },
+ {
+ disableMonitor: true,
+ opcode: "setpixelcolor",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set pixel [x] [y]'s color to [color] in [costume]",
+ arguments: {
+ x: { type: Scratch.ArgumentType.NUMBER, defaultValue: 1 },
+ y: { type: Scratch.ArgumentType.NUMBER, defaultValue: 1 },
+ color: {
+ type: Scratch.ArgumentType.COLOR,
+ defaultValue: "#0000ff",
+ },
+ costume: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "penPlusCostumes",
+ },
+ },
+ },
+ {
+ disableMonitor: true,
+ opcode: "getpixelcolor",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get pixel [x] [y]'s color in [costume]",
+ arguments: {
+ x: { type: Scratch.ArgumentType.NUMBER, defaultValue: 1 },
+ y: { type: Scratch.ArgumentType.NUMBER, defaultValue: 1 },
+ costume: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "penPlusCostumes",
+ },
+ },
+ },
+ {
+ disableMonitor: true,
+ opcode: "getPenPlusCostumeURI",
+ blockType: Scratch.BlockType.REPORTER,
+ text: "get data uri of [costume] in the pen+ costume library",
+ arguments: {
+ costume: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "penPlusCostumes",
+ },
+ },
+ },
+ {
+ opcode: "__NOUSEOPCODE",
+ blockType: Scratch.BlockType.LABEL,
+ text: "Advanced options",
+ },
+ {
+ disableMonitor: true,
+ opcode: "turnAdvancedSettingOff",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "turn advanced setting [Setting] [onOrOff]",
+ arguments: {
+ Setting: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "advancedSettingsMenu",
+ },
+ onOrOff: { type: Scratch.ArgumentType.STRING, menu: "onOffMenu" },
+ },
+ },
+ {
+ disableMonitor: true,
+ opcode: "setAdvancedOptionValueTo",
+ blockType: Scratch.BlockType.COMMAND,
+ text: "set [setting] to [value]",
+ arguments: {
+ setting: {
+ type: Scratch.ArgumentType.STRING,
+ menu: "advancedSettingValuesMenu",
+ },
+ value: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: "1000",
+ },
+ },
+ },
+ ],
+ menus: {
+ hsvMenu: {
+ items: [
+ "color",
+ "saturation",
+ "brightness",
+ "transparency",
+ "size",
+ ],
+ acceptReporters: true,
+ },
+ stampSquare: {
+ items: [
+ { text: "Width", value: "0" },
+ { text: "Height", value: "1" },
+ { text: "Rotation", value: "2" },
+ { text: "U-Multiplier", value: "3" },
+ { text: "U-Offset", value: "4" },
+ { text: "V-Multiplier", value: "5" },
+ { text: "V-Offset", value: "6" },
+ { text: "Red Tint", value: "7" },
+ { text: "Green Tint", value: "8" },
+ { text: "Blue Tint", value: "9" },
+ { text: "Transparency", value: "10" },
+ { text: "depth value", value: "11" },
+ ],
+ acceptReporters: true,
+ },
+ triAttribute: {
+ items: [
+ { text: "U value", value: "0" },
+ { text: "V value", value: "1" },
+ { text: "red tint", value: "2" },
+ { text: "green tint", value: "3" },
+ { text: "blue tint", value: "4" },
+ { text: "transparency", value: "7" },
+ { text: "corner pinch", value: "6" },
+ { text: "depth value", value: "5" },
+ ],
+ acceptReporters: true,
+ },
+ wholeTriAttribute: {
+ items: [
+ { text: "red tint", value: "2" },
+ { text: "green tint", value: "3" },
+ { text: "blue tint", value: "4" },
+ { text: "transparency", value: "7" },
+ { text: "depth value", value: "5" },
+ ],
+ acceptReporters: true,
+ },
+ filterType: {
+ items: [
+ { text: "Closest", value: "9728" },
+ { text: "Linear", value: "9729" },
+ ],
+ acceptReporters: true,
+ },
+ wrapType: {
+ items: [
+ { text: "Clamp", value: "33071" },
+ { text: "Repeat", value: "10497" },
+ { text: "Mirrored", value: "33648" },
+ ],
+ acceptReporters: true,
+ },
+ pointMenu: { items: ["1", "2", "3"], acceptReporters: true },
+ onOffMenu: { items: ["on", "off"], acceptReporters: true },
+ costumeMenu: { items: "costumeMenuFunction", acceptReporters: true },
+ penPlusCostumes: {
+ items: "penPlusCostumesFunction",
+ acceptReporters: true,
+ },
+ advancedSettingsMenu: {
+ items: [
+ { text: "allow 'Corner Pinch < 1'", value: "wValueUnderFlow" },
+ { text: "toggle depth buffer", value: "useDepthBuffer" },
+ { text: "clamp depth value", value: "_ClampZ" },
+ ],
+ acceptReporters: true,
+ },
+ advancedSettingValuesMenu: {
+ items: [{ text: "maximum depth value", value: "depthMax" }],
+ acceptReporters: false,
+ },
+ getCostumeDataURI_costume_Menu: {
+ items: "getCostumeDataURI_costume_MenuFunction",
+ acceptReporters: true,
+ },
+ getDimensionOf_dimension_Menu: {
+ items: ["width", "height"],
+ acceptReporters: true,
+ },
+ },
+ name: "Pen+ V6",
+ id: "penP",
+ menuIconURI:
+ "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSIzMi45OTk3MiIgaGVpZ2h0PSIzMi44ODIwNyIgdmlld0JveD0iMCwwLDMyLjk5OTcyLDMyLjg4MjA3Ij48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMjI0LC0xNjMuOTk5OTMpIj48ZyBkYXRhLXBhcGVyLWRhdGE9InsmcXVvdDtpc1BhaW50aW5nTGF5ZXImcXVvdDs6dHJ1ZX0iIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBzdHJva2UtZGFzaGFycmF5PSIiIHN0cm9rZS1kYXNob2Zmc2V0PSIwIiBzdHlsZT0ibWl4LWJsZW5kLW1vZGU6IG5vcm1hbCI+PHBhdGggZD0iTTIyOC43NTMsMTk0LjYwMmwtNC4yNSwxLjc4bDEuNzgzLC00LjIzN2MxLjIxOCwtMi44OTIgMi45MDcsLTUuNDIzIDUuMDMsLTcuNTM4bDE5Ljc1LC0xOS42NzdjMC44NDYsLTAuODQyIDIuNjUsLTAuNDEgNC4wMzIsMC45NjdjMS4zOCwxLjM3NSAxLjgxNiwzLjE3MyAwLjk3LDQuMDE1bC0xOS43NSwxOS42NzhjLTIuMTIzLDIuMTE2IC00LjY2NCwzLjggLTcuNTY1LDUuMDEyIiBmaWxsPSIjZmZmZmZmIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIHN0cm9rZT0iIzU3NWU3NSIgc3Ryb2tlLXdpZHRoPSIxIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+PHBhdGggZD0iTTIzNi44NTgsMTczLjQyOGMwLDAgMi42MTYsMi4yMiA0LjM1LC0xLjU0NmMzLjc1MiwtOC4xNSA4LjIwMiwtNS43NzIgOC4yMDIsLTUuNzcyIiBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIHN0cm9rZT0iIzU3NWU3NSIgc3Ryb2tlLXdpZHRoPSIxIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+PHBhdGggZD0iTTI1Ni40MiwxNjguODI1YzAsMC40NjMgLTAuMTQsMC44NzMgLTAuNDMyLDEuMTY0bC05LjMzNSw5LjNjMC4yODIsLTAuMjkgMC40MSwtMC42NjggMC40MSwtMS4xMmMwLC0wLjg3NCAtMC41MDcsLTEuOTYzIC0xLjQwNiwtMi44NjhjLTEuMzYyLC0xLjM1OCAtMy4xNDcsLTEuOCAtNC4wMDIsLTAuOTlsOS4zMzUsLTkuMzAxYzAuODQ0LC0wLjg0IDIuNjUsLTAuNDEgNC4wMzUsMC45NmMwLjg5OCwwLjkwNCAxLjM5NiwxLjk4MiAxLjM5NiwyLjg1NU0yMzAuNTE1LDE5My43NzRjLTAuNTczLDAuMzAyIC0xLjE1NywwLjU3IC0xLjc2NCwwLjgzbC00LjI1MSwxLjc3OGwxLjc4NiwtNC4yMzVjMC4yNTgsLTAuNjA0IDAuNTMsLTEuMTg2IDAuODMzLC0xLjc1N2MwLjY5LDAuMTgzIDEuNDQ4LDAuNjI1IDIuMTA4LDEuMjgyYzAuNjYsMC42NTggMS4xMDIsMS40MTIgMS4yODcsMi4xMDIiIGZpbGw9IiM0Yzk3ZmYiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc3Ryb2tlPSIjNTc1ZTc1IiBzdHJva2Utd2lkdGg9IjEiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz48cGF0aCBkPSJNMjU2LjQ5OCwxNjguNzQ4YzAsMC40NjQgLTAuMTQsMC44NzQgLTAuNDMzLDEuMTY1bC0xOS43NDIsMTkuNjhjLTIuMTMsMi4xMSAtNC42NzMsMy43OTMgLTcuNTcyLDUuMDFsLTQuMjUxLDEuNzc3bDAuOTc0LC0yLjMxNmwxLjkyNSwtMC44MDhjMi44OTgsLTEuMjE4IDUuNDQsLTIuOSA3LjU3LC01LjAxbDE5Ljc0MywtMTkuNjhjMC4yOTIsLTAuMjkyIDAuNDMyLC0wLjcwMiAwLjQzMiwtMS4xNjVjMCwtMC42NDYgLTAuMjcsLTEuNCAtMC43OCwtMi4xMjJjMC4yNSwwLjE3MiAwLjUsMC4zNzcgMC43MzcsMC42MTRjMC44OTgsMC45MDUgMS4zOTYsMS45ODMgMS4zOTYsMi44NTYiIGZpbGw9IiM1NzVlNzUiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc3Ryb2tlPSIjNTc1ZTc1IiBzdHJva2Utd2lkdGg9IjEiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIG9wYWNpdHk9IjAuMTUiLz48cGF0aCBkPSJNMjM4LjQ1LDE3Mi44M2MwLDAuNSAtMC40MDQsMC45MDUgLTAuOTA0LDAuOTA1Yy0wLjUsMCAtMC45MDUsLTAuNDA1IC0wLjkwNSwtMC45MDRjMCwtMC41IDAuNDA3LC0wLjkwMyAwLjkwNiwtMC45MDNjMC41LDAgMC45MDQsMC40MDQgMC45MDQsMC45MDR6IiBmaWxsPSIjNTc1ZTc1IiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIHN0cm9rZT0iIzU3NWU3NSIgc3Ryb2tlLXdpZHRoPSIxIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+PHBhdGggZD0iTTI0NC45OTgwNywxODcuMDUyOThoOS41MTc2NSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJub256ZXJvIiBzdHJva2U9IiNmZmZmZmYiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWpvaW49Im1pdGVyIi8+PHBhdGggZD0iTTI0OS43NTY4OSwxOTEuODExOHYtOS41MTc2NSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJub256ZXJvIiBzdHJva2U9IiNmZmZmZmYiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWpvaW49Im1pdGVyIi8+PC9nPjwvZz48L3N2Zz48IS0tcm90YXRpb25DZW50ZXI6MTY6MTYuMDAwMDY5MjMwODQyMTQzLS0+",
+ blockIconURI:
+ "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHZpZXdCb3g9IjAgMCA0MCA0MCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48dGl0bGU+cGVuLWljb248L3RpdGxlPjxnIHN0cm9rZT0iIzU3NUU3NSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxwYXRoIGQ9Ik04Ljc1MyAzNC42MDJsLTQuMjUgMS43OCAxLjc4My00LjIzN2MxLjIxOC0yLjg5MiAyLjkwNy01LjQyMyA1LjAzLTcuNTM4TDMxLjA2NiA0LjkzYy44NDYtLjg0MiAyLjY1LS40MSA0LjAzMi45NjcgMS4zOCAxLjM3NSAxLjgxNiAzLjE3My45NyA0LjAxNUwxNi4zMTggMjkuNTljLTIuMTIzIDIuMTE2LTQuNjY0IDMuOC03LjU2NSA1LjAxMiIgZmlsbD0iI0ZGRiIvPjxwYXRoIGQ9Ik0yOS40MSA2LjExcy00LjQ1LTIuMzc4LTguMjAyIDUuNzcyYy0xLjczNCAzLjc2Ni00LjM1IDEuNTQ2LTQuMzUgMS41NDYiLz48cGF0aCBkPSJNMzYuNDIgOC44MjVjMCAuNDYzLS4xNC44NzMtLjQzMiAxLjE2NGwtOS4zMzUgOS4zYy4yODItLjI5LjQxLS42NjguNDEtMS4xMiAwLS44NzQtLjUwNy0xLjk2My0xLjQwNi0yLjg2OC0xLjM2Mi0xLjM1OC0zLjE0Ny0xLjgtNC4wMDItLjk5TDMwLjk5IDUuMDFjLjg0NC0uODQgMi42NS0uNDEgNC4wMzUuOTYuODk4LjkwNCAxLjM5NiAxLjk4MiAxLjM5NiAyLjg1NU0xMC41MTUgMzMuNzc0Yy0uNTczLjMwMi0xLjE1Ny41Ny0xLjc2NC44M0w0LjUgMzYuMzgybDEuNzg2LTQuMjM1Yy4yNTgtLjYwNC41My0xLjE4Ni44MzMtMS43NTcuNjkuMTgzIDEuNDQ4LjYyNSAyLjEwOCAxLjI4Mi42Ni42NTggMS4xMDIgMS40MTIgMS4yODcgMi4xMDIiIGZpbGw9IiM0Qzk3RkYiLz48cGF0aCBkPSJNMzYuNDk4IDguNzQ4YzAgLjQ2NC0uMTQuODc0LS40MzMgMS4xNjVsLTE5Ljc0MiAxOS42OGMtMi4xMyAyLjExLTQuNjczIDMuNzkzLTcuNTcyIDUuMDFMNC41IDM2LjM4bC45NzQtMi4zMTYgMS45MjUtLjgwOGMyLjg5OC0xLjIxOCA1LjQ0LTIuOSA3LjU3LTUuMDFsMTkuNzQzLTE5LjY4Yy4yOTItLjI5Mi40MzItLjcwMi40MzItMS4xNjUgMC0uNjQ2LS4yNy0xLjQtLjc4LTIuMTIyLjI1LjE3Mi41LjM3Ny43MzcuNjE0Ljg5OC45MDUgMS4zOTYgMS45ODMgMS4zOTYgMi44NTYiIGZpbGw9IiM1NzVFNzUiIG9wYWNpdHk9Ii4xNSIvPjxwYXRoIGQ9Ik0xOC40NSAxMi44M2MwIC41LS40MDQuOTA1LS45MDQuOTA1cy0uOTA1LS40MDUtLjkwNS0uOTA0YzAtLjUuNDA3LS45MDMuOTA2LS45MDMuNSAwIC45MDQuNDA0LjkwNC45MDR6IiBmaWxsPSIjNTc1RTc1Ii8+PC9nPjwvc3ZnPg==",
+ };
+ }
+ costumeMenuFunction() {
+ const myCostumes = runtime._editingTarget.sprite.costumes;
+
+ let readCostumes = [];
+ for (
+ let curCostumeID = 0;
+ curCostumeID < myCostumes.length;
+ curCostumeID++
+ ) {
+ const currentCostume = myCostumes[curCostumeID].name;
+ readCostumes.push(currentCostume);
+ }
+
+ const keys = Object.keys(penPlusCostumeLibrary);
+ if (keys.length > 0) {
+ for (let curCostumeID = 0; curCostumeID < keys.length; curCostumeID++) {
+ const currentCostume = keys[curCostumeID];
+ readCostumes.push(currentCostume);
+ }
+ }
+
+ return readCostumes;
+ }
+ penPlusCostumesFunction() {
+ const readCostumes = [];
+ const keys = Object.keys(penPlusCostumeLibrary);
+ if (keys.length > 0) {
+ for (let curCostumeID = 0; curCostumeID < keys.length; curCostumeID++) {
+ const currentCostume = keys[curCostumeID];
+ readCostumes.push(currentCostume);
+ }
+ return readCostumes;
+ }
+
+ return ["no pen+ costumes!"];
+ }
+ isPenDown(args, util) {
+ checkForPen(util);
+ const curTarget = util.target;
+ return curTarget["_customState"]["Scratch.pen"].penDown;
+ }
+ getPenHSV({ HSV }, util) {
+ checkForPen(util);
+ const curTarget = util.target;
+ if (HSV == "size") {
+ return curTarget["_customState"]["Scratch.pen"].penAttributes.diameter;
+ }
+ return curTarget["_customState"]["Scratch.pen"][HSV];
+ }
+ drawDot({ x, y }, util) {
+ checkForPen(util);
+ const curTarget = util.target;
+ const attrib = curTarget["_customState"]["Scratch.pen"].penAttributes;
+
+ curTarget.runtime.ext_pen.penDown(null, util);
+
+ Scratch.vm.renderer.penPoint(
+ Scratch.vm.renderer._penSkinId,
+ attrib,
+ x,
+ y
+ );
+
+ curTarget.runtime.ext_pen.penUp(null, util);
+ }
+ drawLine({ x1, y1, x2, y2 }, util) {
+ checkForPen(util);
+ const curTarget = util.target;
+ const attrib = curTarget["_customState"]["Scratch.pen"].penAttributes;
+
+ curTarget.runtime.ext_pen.penDown(null, util);
+
+ Scratch.vm.renderer.penLine(
+ Scratch.vm.renderer._penSkinId,
+ attrib,
+ x1,
+ y1,
+ x2,
+ y2
+ );
+
+ curTarget.runtime.ext_pen.penUp(null, util);
+ }
+ squareDown(arg, util) {
+ //Just a simple thing to allow for pen drawing
+ const curTarget = util.target;
+
+ checkForPen(util);
+
+ const attrib = curTarget["_customState"]["Scratch.pen"].penAttributes;
+ const diam = attrib.diameter;
+
+ nativeSize = renderer.useHighQualityRender
+ ? [canvas.width, canvas.height]
+ : renderer._nativeSize;
+
+ lilPenDabble(nativeSize, curTarget, util); // Do this so the renderer doesn't scream at us
+
+ if (
+ typeof triangleAttributesOfAllSprites["squareStamp_" + curTarget.id] ==
+ "undefined"
+ ) {
+ triangleAttributesOfAllSprites["squareStamp_" + curTarget.id] =
+ triangleDefaultAttributes;
+ }
+
+ if (typeof squareAttributesOfAllSprites[curTarget.id] == "undefined") {
+ squareAttributesOfAllSprites[curTarget.id] = squareDefaultAttributes;
+ }
+
+ const myAttributes = squareAttributesOfAllSprites[curTarget.id];
+
+ //trying my best to reduce memory usage
+ gl.viewport(0, 0, nativeSize[0], nativeSize[1]);
+ const dWidth = 1 / nativeSize[0];
+ const dHeight = 1 / nativeSize[1];
+
+ const spritex = curTarget.x;
+ const spritey = curTarget.y;
+
+ //correction for HQ pen
+ const typSize = renderer._nativeSize;
+ const mul = renderer.useHighQualityRender
+ ? 2 * ((canvas.width + canvas.height) / (typSize[0] + typSize[1]))
+ : 2;
+
+ //Predifine stuff so there aren't as many calculations
+ const wMulX = mul * myAttributes[0];
+ const wMulY = mul * myAttributes[1];
+
+ const offDiam = 0.5 * diam;
+
+ const sprXoff = spritex * mul;
+ const sprYoff = spritey * mul;
+ //Paratheses because I know some obscure browser will screw this up.
+ let x1 = Scratch.Cast.toNumber(-offDiam) * wMulX;
+ let x2 = Scratch.Cast.toNumber(offDiam) * wMulX;
+ let x3 = Scratch.Cast.toNumber(offDiam) * wMulX;
+ let x4 = Scratch.Cast.toNumber(-offDiam) * wMulX;
+
+ let y1 = Scratch.Cast.toNumber(offDiam) * wMulY;
+ let y2 = Scratch.Cast.toNumber(offDiam) * wMulY;
+ let y3 = Scratch.Cast.toNumber(-offDiam) * wMulY;
+ let y4 = Scratch.Cast.toNumber(-offDiam) * wMulY;
+
+ function rotateTheThings(ox1, oy1, ox2, oy2, ox3, oy3, ox4, oy4) {
+ let sin = Math.sin(myAttributes[2] * d2r);
+ let cos = Math.cos(myAttributes[2] * d2r);
+
+ x1 = ox1 * sin + oy1 * cos;
+ y1 = ox1 * cos - oy1 * sin;
+
+ x2 = ox2 * sin + oy2 * cos;
+ y2 = ox2 * cos - oy2 * sin;
+
+ x3 = ox3 * sin + oy3 * cos;
+ y3 = ox3 * cos - oy3 * sin;
+
+ x4 = ox4 * sin + oy4 * cos;
+ y4 = ox4 * cos - oy4 * sin;
+ }
+
+ rotateTheThings(x1, y1, x2, y2, x3, y3, x4, y4);
+
+ x1 += sprXoff;
+ y1 += sprYoff;
+
+ x2 += sprXoff;
+ y2 += sprYoff;
+
+ x3 += sprXoff;
+ y3 += sprYoff;
+
+ x4 += sprXoff;
+ y4 += sprYoff;
+
+ x1 *= dWidth;
+ y1 *= dHeight;
+
+ x2 *= dWidth;
+ y2 *= dHeight;
+
+ x3 *= dWidth;
+ y3 *= dHeight;
+
+ x4 *= dWidth;
+ y4 *= dHeight;
+
+ const Attribute_ID = "squareStamp_" + curTarget.id;
+
+ triangleAttributesOfAllSprites[Attribute_ID][2] = myAttributes[7];
+ triangleAttributesOfAllSprites[Attribute_ID][3] = myAttributes[8];
+ triangleAttributesOfAllSprites[Attribute_ID][4] = myAttributes[9];
+ triangleAttributesOfAllSprites[Attribute_ID][5] = myAttributes[11];
+ triangleAttributesOfAllSprites[Attribute_ID][7] = myAttributes[10];
+ triangleAttributesOfAllSprites[Attribute_ID][10] = myAttributes[7];
+ triangleAttributesOfAllSprites[Attribute_ID][11] = myAttributes[8];
+ triangleAttributesOfAllSprites[Attribute_ID][12] = myAttributes[9];
+ triangleAttributesOfAllSprites[Attribute_ID][13] = myAttributes[11];
+ triangleAttributesOfAllSprites[Attribute_ID][15] = myAttributes[10];
+ triangleAttributesOfAllSprites[Attribute_ID][18] = myAttributes[7];
+ triangleAttributesOfAllSprites[Attribute_ID][19] = myAttributes[8];
+ triangleAttributesOfAllSprites[Attribute_ID][20] = myAttributes[9];
+ triangleAttributesOfAllSprites[Attribute_ID][21] = myAttributes[11];
+ triangleAttributesOfAllSprites[Attribute_ID][23] = myAttributes[10];
+
+ triFunctions.drawTri(
+ gl.getParameter(gl.CURRENT_PROGRAM),
+ x1,
+ y1,
+ x2,
+ y2,
+ x3,
+ y3,
+ attrib.color4f,
+ Attribute_ID
+ );
+
+ triFunctions.drawTri(
+ gl.getParameter(gl.CURRENT_PROGRAM),
+ x1,
+ y1,
+ x3,
+ y3,
+ x4,
+ y4,
+ attrib.color4f,
+ Attribute_ID
+ );
+ }
+ squareTexDown({ tex }, util) {
+ //Just a simple thing to allow for pen drawing
+ const curTarget = util.target;
+
+ let currentTexture = null;
+ if (penPlusCostumeLibrary[tex]) {
+ currentTexture = penPlusCostumeLibrary[tex].texture;
+ } else {
+ const costIndex = curTarget.getCostumeIndexByName(
+ Scratch.Cast.toString(tex)
+ );
+ if (costIndex >= 0) {
+ const curCostume = curTarget.sprite.costumes_[costIndex];
+ if (costIndex != curTarget.currentCostume) {
+ curTarget.setCostume(costIndex);
+ }
+
+ currentTexture = renderer._allSkins[curCostume.skinId].getTexture();
+ }
+ }
+
+ checkForPen(util);
+
+ const attrib = curTarget["_customState"]["Scratch.pen"].penAttributes;
+ const diam = attrib.diameter;
+
+ nativeSize = renderer.useHighQualityRender
+ ? [canvas.width, canvas.height]
+ : renderer._nativeSize;
+
+ lilPenDabble(nativeSize, curTarget, util); // Do this so the renderer doesn't scream at us
+
+ if (!triangleAttributesOfAllSprites["squareStamp_" + curTarget.id]) {
+ triangleAttributesOfAllSprites["squareStamp_" + curTarget.id] =
+ triangleDefaultAttributes;
+ }
+
+ if (!squareAttributesOfAllSprites[curTarget.id]) {
+ squareAttributesOfAllSprites[curTarget.id] = squareDefaultAttributes;
+ }
+
+ const myAttributes = squareAttributesOfAllSprites[curTarget.id];
+
+ //trying my best to reduce memory usage
+ gl.viewport(0, 0, nativeSize[0], nativeSize[1]);
+ const dWidth = 1 / nativeSize[0];
+ const dHeight = 1 / nativeSize[1];
+
+ const spritex = curTarget.x;
+ const spritey = curTarget.y;
+
+ //correction for HQ pen
+ const typSize = renderer._nativeSize;
+ const mul = renderer.useHighQualityRender
+ ? 2 * ((canvas.width + canvas.height) / (typSize[0] + typSize[1]))
+ : 2;
+
+ //Predifine stuff so there aren't as many calculations
+ const wMulX = mul * myAttributes[0];
+ const wMulY = mul * myAttributes[1];
+
+ const offDiam = 0.5 * diam;
+
+ const sprXoff = spritex * mul;
+ const sprYoff = spritey * mul;
+ //Paratheses because I know some obscure browser will screw this up.
+ let x1 = Scratch.Cast.toNumber(-offDiam) * wMulX;
+ let x2 = Scratch.Cast.toNumber(offDiam) * wMulX;
+ let x3 = Scratch.Cast.toNumber(offDiam) * wMulX;
+ let x4 = Scratch.Cast.toNumber(-offDiam) * wMulX;
+
+ let y1 = Scratch.Cast.toNumber(offDiam) * wMulY;
+ let y2 = Scratch.Cast.toNumber(offDiam) * wMulY;
+ let y3 = Scratch.Cast.toNumber(-offDiam) * wMulY;
+ let y4 = Scratch.Cast.toNumber(-offDiam) * wMulY;
+
+ function rotateTheThings(ox1, oy1, ox2, oy2, ox3, oy3, ox4, oy4) {
+ let sin = Math.sin(myAttributes[2] * d2r);
+ let cos = Math.cos(myAttributes[2] * d2r);
+
+ x1 = ox1 * sin + oy1 * cos;
+ y1 = ox1 * cos - oy1 * sin;
+
+ x2 = ox2 * sin + oy2 * cos;
+ y2 = ox2 * cos - oy2 * sin;
+
+ x3 = ox3 * sin + oy3 * cos;
+ y3 = ox3 * cos - oy3 * sin;
+
+ x4 = ox4 * sin + oy4 * cos;
+ y4 = ox4 * cos - oy4 * sin;
+ }
+
+ rotateTheThings(x1, y1, x2, y2, x3, y3, x4, y4);
+
+ x1 += sprXoff;
+ y1 += sprYoff;
+
+ x2 += sprXoff;
+ y2 += sprYoff;
+
+ x3 += sprXoff;
+ y3 += sprYoff;
+
+ x4 += sprXoff;
+ y4 += sprYoff;
+
+ x1 *= dWidth;
+ y1 *= dHeight;
+
+ x2 *= dWidth;
+ y2 *= dHeight;
+
+ x3 *= dWidth;
+ y3 *= dHeight;
+
+ x4 *= dWidth;
+ y4 *= dHeight;
+
+ if (currentTexture != null && typeof currentTexture != "undefined") {
+ const Attribute_ID = "squareStamp_" + curTarget.id;
+ triangleAttributesOfAllSprites[Attribute_ID][0] =
+ (0 + myAttributes[4]) * myAttributes[3];
+ triangleAttributesOfAllSprites[Attribute_ID][1] =
+ (1 + myAttributes[6]) * myAttributes[5];
+
+ triangleAttributesOfAllSprites[Attribute_ID][2] = myAttributes[7];
+ triangleAttributesOfAllSprites[Attribute_ID][3] = myAttributes[8];
+ triangleAttributesOfAllSprites[Attribute_ID][4] = myAttributes[9];
+ triangleAttributesOfAllSprites[Attribute_ID][5] = myAttributes[11];
+ triangleAttributesOfAllSprites[Attribute_ID][7] = myAttributes[10];
+
+ triangleAttributesOfAllSprites[Attribute_ID][8] =
+ (1 + myAttributes[4]) * myAttributes[3];
+ triangleAttributesOfAllSprites[Attribute_ID][9] =
+ (1 + myAttributes[6]) * myAttributes[5];
+
+ triangleAttributesOfAllSprites[Attribute_ID][10] = myAttributes[7];
+ triangleAttributesOfAllSprites[Attribute_ID][11] = myAttributes[8];
+ triangleAttributesOfAllSprites[Attribute_ID][12] = myAttributes[9];
+ triangleAttributesOfAllSprites[Attribute_ID][13] = myAttributes[11];
+ triangleAttributesOfAllSprites[Attribute_ID][15] = myAttributes[10];
+
+ triangleAttributesOfAllSprites[Attribute_ID][16] =
+ (1 + myAttributes[4]) * myAttributes[3];
+ triangleAttributesOfAllSprites[Attribute_ID][17] =
+ (0 + myAttributes[6]) * myAttributes[5];
+
+ triangleAttributesOfAllSprites[Attribute_ID][18] = myAttributes[7];
+ triangleAttributesOfAllSprites[Attribute_ID][19] = myAttributes[8];
+ triangleAttributesOfAllSprites[Attribute_ID][20] = myAttributes[9];
+ triangleAttributesOfAllSprites[Attribute_ID][21] = myAttributes[11];
+ triangleAttributesOfAllSprites[Attribute_ID][23] = myAttributes[10];
+
+ triFunctions.drawTextTri(
+ gl.getParameter(gl.CURRENT_PROGRAM),
+ x1,
+ y1,
+ x2,
+ y2,
+ x3,
+ y3,
+ Attribute_ID,
+ currentTexture
+ );
+
+ triangleAttributesOfAllSprites[Attribute_ID][0] =
+ (0 + myAttributes[4]) * myAttributes[3];
+ triangleAttributesOfAllSprites[Attribute_ID][1] =
+ (1 + myAttributes[6]) * myAttributes[5];
+
+ triangleAttributesOfAllSprites[Attribute_ID][8] =
+ (1 + myAttributes[4]) * myAttributes[3];
+ triangleAttributesOfAllSprites[Attribute_ID][9] =
+ (0 + myAttributes[6]) * myAttributes[5];
+
+ triangleAttributesOfAllSprites[Attribute_ID][16] =
+ (0 + myAttributes[4]) * myAttributes[3];
+ triangleAttributesOfAllSprites[Attribute_ID][17] =
+ (0 + myAttributes[6]) * myAttributes[5];
+
+ triFunctions.drawTextTri(
+ gl.getParameter(gl.CURRENT_PROGRAM),
+ x1,
+ y1,
+ x3,
+ y3,
+ x4,
+ y4,
+ Attribute_ID,
+ currentTexture
+ );
+ }
+ }
+ setStampAttribute({ target, number }, util) {
+ const curTarget = util.target;
+ if (!squareAttributesOfAllSprites[curTarget.id]) {
+ squareAttributesOfAllSprites[curTarget.id] = squareDefaultAttributes;
+ }
+
+ let valuetoSet = 0;
+
+ const attributeNum = Scratch.Cast.toNumber(target);
+ if (attributeNum >= 7) {
+ if (attributeNum == 11) {
+ if (penPlusAdvancedSettings._ClampZ) {
+ Math.min(
+ Math.max(number / penPlusAdvancedSettings._maxDepth, 0),
+ 1
+ );
+ return;
+ }
+ valuetoSet = number / penPlusAdvancedSettings._maxDepth;
+ squareAttributesOfAllSprites[curTarget.id][attributeNum] =
+ number / penPlusAdvancedSettings._maxDepth;
+ return;
+ }
+ squareAttributesOfAllSprites[curTarget.id][attributeNum] =
+ Math.min(Math.max(number, 0), 100) * 0.01;
+ return;
+ }
+ squareAttributesOfAllSprites[curTarget.id][attributeNum] = number;
+ }
+ getStampAttribute({ target }, util) {
+ const curTarget = util.target;
+ if (!squareAttributesOfAllSprites[curTarget.id]) {
+ squareAttributesOfAllSprites[curTarget.id] = squareDefaultAttributes;
+ }
+
+ return squareAttributesOfAllSprites[curTarget.id][
+ Scratch.Cast.toNumber(target)
+ ];
+ }
+ tintSquare({ color }, util) {
+ const curTarget = util.target;
+
+ if (!squareAttributesOfAllSprites[curTarget.id]) {
+ squareAttributesOfAllSprites[curTarget.id] = squareDefaultAttributes;
+ }
+
+ const calcColor = colors.hexToRgb(color);
+
+ squareAttributesOfAllSprites[curTarget.id][7] = calcColor.r / 255;
+ squareAttributesOfAllSprites[curTarget.id][8] = calcColor.g / 255;
+ squareAttributesOfAllSprites[curTarget.id][9] = calcColor.b / 255;
+ }
+ resetSquareAttributes(args, util) {
+ const curTarget = util.target;
+ squareAttributesOfAllSprites[curTarget.id] = [
+ 1, 1, 90, 1, 0, 1, 0, 1, 1, 1, 1, 0,
+ ];
+ }
+ setTriangleFilterMode({ filter }) {
+ currentFilter = filter;
+ }
+ setTrianglePointAttribute({ point, attribute, value }, util) {
+ const trianglePointStart = (point - 1) * 8;
+
+ const targetId = util.target.id;
+
+ if (!triangleAttributesOfAllSprites[targetId]) {
+ triangleAttributesOfAllSprites[targetId] = triangleDefaultAttributes;
+ }
+ triFunctions.setValueAccordingToCaseTriangle(
+ targetId,
+ Scratch.Cast.toNumber(attribute),
+ value,
+ false,
+ trianglePointStart
+ );
+ }
+ setWholeTrianglePointAttribute({ wholeAttribute, value }, util) {
+ const targetId = util.target.id;
+
+ if (!triangleAttributesOfAllSprites[targetId]) {
+ triangleAttributesOfAllSprites[targetId] = triangleDefaultAttributes;
+ }
+ triFunctions.setValueAccordingToCaseTriangle(
+ targetId,
+ Scratch.Cast.toNumber(wholeAttribute),
+ value,
+ true,
+ 0
+ );
+ }
+ tintTriPoint({ point, color }, util) {
+ const curTarget = util.target;
+
+ const trianglePointStart = (point - 1) * 8;
+
+ const targetId = util.target.id;
+
+ if (!triangleAttributesOfAllSprites[targetId]) {
+ triangleAttributesOfAllSprites[targetId] = triangleDefaultAttributes;
+ }
+
+ const calcColor = colors.hexToRgb(color);
+
+ triFunctions.setValueAccordingToCaseTriangle(
+ targetId,
+ 2,
+ calcColor.r / 2.55,
+ false,
+ trianglePointStart
+ );
+
+ triFunctions.setValueAccordingToCaseTriangle(
+ targetId,
+ 3,
+ calcColor.g / 2.55,
+ false,
+ trianglePointStart
+ );
+
+ triFunctions.setValueAccordingToCaseTriangle(
+ targetId,
+ 4,
+ calcColor.b / 2.55,
+ false,
+ trianglePointStart
+ );
+ }
+ tintTri({ point, color }, util) {
+ const curTarget = util.target;
+
+ const trianglePointStart = (point - 1) * 8;
+
+ const targetId = util.target.id;
+
+ if (!triangleAttributesOfAllSprites[targetId]) {
+ triangleAttributesOfAllSprites[targetId] = triangleDefaultAttributes;
+ }
+
+ const calcColor = colors.hexToRgb(color);
+
+ triFunctions.setValueAccordingToCaseTriangle(
+ targetId,
+ 2,
+ calcColor.r / 2.55,
+ true,
+ trianglePointStart
+ );
+
+ triFunctions.setValueAccordingToCaseTriangle(
+ targetId,
+ 3,
+ calcColor.g / 2.55,
+ true,
+ trianglePointStart
+ );
+
+ triFunctions.setValueAccordingToCaseTriangle(
+ targetId,
+ 4,
+ calcColor.b / 2.55,
+ true,
+ trianglePointStart
+ );
+ }
+ getTrianglePointAttribute({ point, attribute }, util) {
+ const trianglePointStart = (point - 1) * 8;
+
+ const targetId = util.target.id;
+
+ if (!triangleAttributesOfAllSprites[targetId]) {
+ triangleAttributesOfAllSprites[targetId] = triangleDefaultAttributes;
+ }
+ let value =
+ triangleAttributesOfAllSprites[targetId][
+ trianglePointStart + attribute
+ ];
+
+ if ((attribute >= 2 && attribute <= 4) || attribute == 7) {
+ value *= 100;
+ }
+ return value;
+ }
+ resetWholeTriangleAttributes(args, util) {
+ const targetId = util.target.id;
+ triangleAttributesOfAllSprites[targetId] = [
+ 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1,
+ 1, 1, 1,
+ ];
+ }
+ drawSolidTri({ x1, y1, x2, y2, x3, y3 }, util) {
+ const curTarget = util.target;
+ checkForPen(util);
+ const attrib = curTarget["_customState"]["Scratch.pen"].penAttributes;
+
+ nativeSize = renderer.useHighQualityRender
+ ? [canvas.width, canvas.height]
+ : renderer._nativeSize;
+
+ //if (triangleAttributesOfAllSprites[curTarget.id]) {
+ // triangleAttributesOfAllSprites[curTarget.id][5] = 1;
+ // triangleAttributesOfAllSprites[curTarget.id][13] = 1;
+ // triangleAttributesOfAllSprites[curTarget.id][21] = 1;
+ //}
+
+ //?Renderer Freaks out if we don't do this so do it.
+ lilPenDabble(nativeSize, curTarget, util); // Do this so the renderer doesn't scream at us
+
+ //trying my best to reduce memory usage
+ gl.viewport(0, 0, nativeSize[0], nativeSize[1]);
+ const dWidth = 1 / nativeSize[0];
+ const dHeight = 1 / nativeSize[1];
+
+ //correction for HQ pen
+ const typSize = renderer._nativeSize;
+ const mul = renderer.useHighQualityRender
+ ? 2 * ((canvas.width + canvas.height) / (typSize[0] + typSize[1]))
+ : 2;
+ //Paratheses because I know some obscure browser will screw this up.
+ x1 = Scratch.Cast.toNumber(x1) * dWidth * mul;
+ x2 = Scratch.Cast.toNumber(x2) * dWidth * mul;
+ x3 = Scratch.Cast.toNumber(x3) * dWidth * mul;
+
+ y1 = Scratch.Cast.toNumber(y1) * dHeight * mul;
+ y2 = Scratch.Cast.toNumber(y2) * dHeight * mul;
+ y3 = Scratch.Cast.toNumber(y3) * dHeight * mul;
+
+ triFunctions.drawTri(
+ gl.getParameter(gl.CURRENT_PROGRAM),
+ x1,
+ y1,
+ x2,
+ y2,
+ x3,
+ y3,
+ attrib.color4f,
+ curTarget.id
+ );
+ }
+ drawTexTri({ x1, y1, x2, y2, x3, y3, tex }, util) {
+ const curTarget = util.target;
+ let currentTexture = null;
+ if (penPlusCostumeLibrary[tex]) {
+ currentTexture = penPlusCostumeLibrary[tex].texture;
+ } else {
+ const costIndex = curTarget.getCostumeIndexByName(
+ Scratch.Cast.toString(tex)
+ );
+ if (costIndex >= 0) {
+ const curCostume = curTarget.sprite.costumes_[costIndex];
+ if (costIndex != curTarget.currentCostume) {
+ curTarget.setCostume(costIndex);
+ }
+
+ currentTexture = renderer._allSkins[curCostume.skinId].getTexture();
+ }
+ }
+
+ nativeSize = renderer.useHighQualityRender
+ ? [canvas.width, canvas.height]
+ : renderer._nativeSize;
+
+ //?Renderer Freaks out if we don't do this so do it.
+ lilPenDabble(nativeSize, curTarget, util); // Do this so the renderer doesn't scream at us
+
+ //trying my best to reduce memory usage
+ gl.viewport(0, 0, nativeSize[0], nativeSize[1]);
+ const dWidth = 1 / nativeSize[0];
+ const dHeight = 1 / nativeSize[1];
+
+ //correction for HQ pen
+ const typSize = renderer._nativeSize;
+ const mul = renderer.useHighQualityRender
+ ? 2 * ((canvas.width + canvas.height) / (typSize[0] + typSize[1]))
+ : 2;
+ //Paratheses because I know some obscure browser will screw this up.
+ x1 = Scratch.Cast.toNumber(x1) * dWidth * mul;
+ x2 = Scratch.Cast.toNumber(x2) * dWidth * mul;
+ x3 = Scratch.Cast.toNumber(x3) * dWidth * mul;
+
+ y1 = Scratch.Cast.toNumber(y1) * dHeight * mul;
+ y2 = Scratch.Cast.toNumber(y2) * dHeight * mul;
+ y3 = Scratch.Cast.toNumber(y3) * dHeight * mul;
+
+ if (currentTexture != null && typeof currentTexture != "undefined") {
+ triFunctions.drawTextTri(
+ gl.getParameter(gl.CURRENT_PROGRAM),
+ x1,
+ y1,
+ x2,
+ y2,
+ x3,
+ y3,
+ curTarget.id,
+ currentTexture
+ );
+ }
+ }
+ RGB2HEX({ R, G, B }) {
+ return colors.rgbtoSColor({ R: R, G: G, B: B });
+ }
+ HSV2RGB({ H, S, V }) {
+ S = S / 100;
+ V = V / 100;
+ S = Math.min(Math.max(S, 0), 1);
+ V = Math.min(Math.max(V, 0), 1);
+ H = H % 360;
+ const C = V * S;
+ const X = C * (1 - Math.abs(((H / 60) % 2) - 1));
+ const M = V - C;
+ let Primes = [0, 0, 0];
+ if (H >= 0 && H < 60) {
+ Primes[0] = C;
+ Primes[1] = X;
+ } else if (H >= 60 && H < 120) {
+ Primes[0] = X;
+ Primes[1] = C;
+ } else if (H >= 120 && H < 180) {
+ Primes[1] = C;
+ Primes[2] = X;
+ } else if (H >= 180 && H < 240) {
+ Primes[1] = X;
+ Primes[2] = C;
+ } else if (H >= 240 && H < 300) {
+ Primes[0] = X;
+ Primes[2] = C;
+ }
+ if (H >= 300 && H < 360) {
+ Primes[0] = C;
+ Primes[2] = X;
+ }
+ Primes[0] = (Primes[0] + M) * 255;
+ Primes[1] = (Primes[1] + M) * 255;
+ Primes[2] = (Primes[2] + M) * 255;
+ return colors.rgbtoSColor({
+ R: Primes[0] / 2.55,
+ G: Primes[1] / 2.55,
+ B: Primes[2] / 2.55,
+ });
+ }
+ setDURIclampmode({ clampMode }) {
+ penPlusImportWrapMode = clampMode;
+ }
+ addBlankIMG({ color, width, height, name }) {
+ //Just a simple thing to allow for pen drawing
+ textureFunctions.createBlankPenPlusTextureInfo(
+ width,
+ height,
+ color,
+ "!" + name,
+ penPlusImportWrapMode
+ );
+ }
+ addIMGfromDURI({ dataURI, name }) {
+ //Just a simple thing to allow for pen drawing
+ textureFunctions.createPenPlusTextureInfo(
+ dataURI,
+ "!" + name,
+ penPlusImportWrapMode
+ );
+ }
+ removeIMGfromDURI({ name }, util) {
+ //Just a simple thing to allow for pen drawing
+ if (penPlusCostumeLibrary["!" + name]) {
+ delete penPlusCostumeLibrary["!" + name];
+ }
+ }
+ doesIMGexist({ name }, util) {
+ //Just a simple thing to allow for pen drawing
+ return typeof penPlusCostumeLibrary["!" + name] != "undefined";
+ }
+ getCostumeDataURI({ costume }, util) {
+ //Just a simple thing to allow for pen drawing
+ const curTarget = util.target;
+ const costIndex = curTarget.getCostumeIndexByName(
+ Scratch.Cast.toString(costume)
+ );
+ if (costIndex >= 0) {
+ const curCostume =
+ curTarget.sprite.costumes_[costIndex].asset.encodeDataURI();
+ return curCostume;
+ }
+ }
+ getCostumeDataURI_costume_MenuFunction() {
+ const myCostumes = runtime._editingTarget.sprite.costumes;
+
+ let readCostumes = [];
+ for (
+ let curCostumeID = 0;
+ curCostumeID < myCostumes.length;
+ curCostumeID++
+ ) {
+ const currentCostume = myCostumes[curCostumeID].name;
+ readCostumes.push(currentCostume);
+ }
+
+ return readCostumes;
+ }
+ getDimensionOf({ dimension, costume }, util) {
+ //Just a simple thing to allow for pen drawing
+ const costIndex = penPlusCostumeLibrary[costume];
+ if (costIndex) {
+ return costIndex[dimension];
+ }
+ }
+ setpixelcolor({ x, y, color, costume }) {
+ const curCostume = penPlusCostumeLibrary[costume];
+ if (curCostume) {
+ const textureData = textureFunctions.getTextureData(
+ curCostume.texture,
+ curCostume.width,
+ curCostume.height
+ );
+ if (textureData) {
+ x = Math.floor(x - 1);
+ y = Math.floor(y - 1);
+ const colorIndex = (y * curCostume.width + x) * 4;
+ if (
+ textureData[colorIndex] != undefined &&
+ x < curCostume.width &&
+ x >= 0
+ ) {
+ const retColor = colors.hexToRgb(color);
+ textureData[colorIndex] = retColor.r;
+ textureData[colorIndex + 1] = retColor.g;
+ textureData[colorIndex + 2] = retColor.b;
+ textureData[colorIndex + 3] = 255;
+
+ gl.bindTexture(gl.TEXTURE_2D, curCostume.texture);
+ gl.texImage2D(
+ gl.TEXTURE_2D,
+ 0,
+ gl.RGBA,
+ curCostume.width,
+ curCostume.height,
+ 0,
+ gl.RGBA,
+ gl.UNSIGNED_BYTE,
+ textureData
+ );
+ }
+ }
+ }
+ }
+ getpixelcolor({ x, y, costume }) {
+ const curCostume = penPlusCostumeLibrary[costume];
+ if (curCostume) {
+ const textureData = textureFunctions.getTextureData(
+ curCostume.texture,
+ curCostume.width,
+ curCostume.height
+ );
+ if (textureData) {
+ x = Math.floor(x - 1);
+ y = Math.floor(y - 1);
+ const colorIndex = (y * curCostume.width + x) * 4;
+ if (textureData[colorIndex] && x < curCostume.width && x >= 0) {
+ return colors.rgbtoSColor({
+ R: textureData[colorIndex] / 2.55,
+ G: textureData[colorIndex + 1] / 2.55,
+ B: textureData[colorIndex + 2] / 2.55,
+ });
+ }
+ return colors.rgbtoSColor({ R: 100, G: 100, B: 100 });
+ }
+ }
+ }
+ getPenPlusCostumeURI({ costume }) {
+ const curCostume = penPlusCostumeLibrary[costume];
+ if (curCostume) {
+ const textureData = textureFunctions.getTextureAsURI(
+ curCostume.texture,
+ curCostume.width,
+ curCostume.height
+ );
+ if (textureData) {
+ return textureData;
+ }
+ return "";
+ }
+ }
+ turnAdvancedSettingOff({ Setting, onOrOff }) {
+ if (onOrOff == "on") {
+ penPlusAdvancedSettings[Setting] = true;
+ return;
+ }
+ penPlusAdvancedSettings[Setting] = false;
+ }
+ setAdvancedOptionValueTo({ setting, value }) {
+ switch (setting) {
+ case "depthMax":
+ penPlusAdvancedSettings._maxDepth = Math.max(value, 100);
+ break;
+
+ default:
+ break;
+ }
+ }
+ runtime = Scratch.vm.runtime;
+ }
+
+ //? A small hack to stop the renderer from immediatly dying. And to allow for immediate use
+ {
+ if (!Scratch.vm.renderer._penSkinId) {
+ window.vm.renderer.createPenSkin();
+ }
+ renderer.penClear(Scratch.vm.renderer._penSkinId);
+ Scratch.vm.renderer.penLine(
+ Scratch.vm.renderer._penSkinId,
+ {
+ color4f: [0, 0, 1, 1],
+ diameter: 1,
+ },
+ 0,
+ 0,
+ 0,
+ 0
+ );
+
+ penPlusShaders.pen.program = shaderManager._shaderCache.line[0].program;
+ }
+
+ Scratch.extensions.register(new extension());
+})(Scratch);
diff --git a/extensions/penplus.js b/extensions/penplus.js
index fb11d2de3c..79a5b47c3d 100644
--- a/extensions/penplus.js
+++ b/extensions/penplus.js
@@ -1,6 +1,6 @@
-// Name: Pen Plus
+// Name: Pen Plus V5 (Old)
// ID: betterpen
-// Description: Advanced rendering capabilities.
+// Description: Replaced by Pen Plus V6.
// By: ObviousAlexC
/* eslint-disable no-empty-pattern */
@@ -2422,7 +2422,7 @@ Other various small fixes
getInfo() {
return {
id: "betterpen",
- name: "Pen+",
+ name: "Pen+ V5",
color1: "#0e9a6b",
color2: "#0b7f58",
color3: "#096647",