diff --git a/Cache/ClipAndBlend.js b/Cache/ClipAndBlend.js new file mode 100644 index 0000000..43ce0d1 --- /dev/null +++ b/Cache/ClipAndBlend.js @@ -0,0 +1,506 @@ +// I cached this to see if its just this version that i need to use in origin + + +(function(Scratch) { // Name: Clipping & Blending +// ID: xeltallivclipblend +// Description: Clipping outside of a specified rectangular area and different color blending modes. +// By: Vadik1 +// License: MIT + +(function (Scratch) { + "use strict"; + + if (!Scratch.extensions.unsandboxed) { + throw new Error("Clipping & Blending extension must be run unsandboxed"); + } + + // Simplified remake of an icon by True-Fantom + const icon = + "data:image/svg+xml," + + encodeURIComponent(` + + + + + + + + + `); + + let toCorrectThing = null; + let active = false; + let flipY = false; + const vm = Scratch.vm; + const runtime = vm.runtime; + const renderer = vm.renderer; + const _drawThese = renderer._drawThese; + const gl = renderer._gl; + const canvas = renderer.canvas; + let width = 0; + let height = 0; + let scratchUnitWidth = 480; + let scratchUnitHeight = 360; + let penDirty = false; + + renderer._drawThese = function (drawables, drawMode, projection, opts) { + active = true; + [scratchUnitWidth, scratchUnitHeight] = renderer.getNativeSize(); + _drawThese.call(this, drawables, drawMode, projection, opts); + gl.disable(gl.SCISSOR_TEST); + gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); + active = false; + }; + + const bfb = gl.bindFramebuffer; + gl.bindFramebuffer = function (target, framebuffer) { + if (target == gl.FRAMEBUFFER) { + if (framebuffer == null) { + toCorrectThing = true; + flipY = false; + width = canvas.width; + height = canvas.height; + } else if (renderer._penSkinId) { + const fbInfo = renderer._allSkins[renderer._penSkinId]._framebuffer; + if (framebuffer == fbInfo.framebuffer) { + toCorrectThing = true; + flipY = true; + width = fbInfo.width; + height = fbInfo.height; + } else { + toCorrectThing = false; + } + } else { + toCorrectThing = false; + } + } + bfb.call(this, target, framebuffer); + }; + + // Getting Drawable + const dr = renderer.createDrawable("background"); + const DrawableProto = renderer._allDrawables[dr].__proto__; + renderer.destroyDrawable(dr, "background"); + + function setupModes(clipbox, blendMode, flipY) { + if (clipbox) { + gl.enable(gl.SCISSOR_TEST); + let x = ((clipbox.x_min / scratchUnitWidth + 0.5) * width) | 0; + let y = ((clipbox.y_min / scratchUnitHeight + 0.5) * height) | 0; + let x2 = ((clipbox.x_max / scratchUnitWidth + 0.5) * width) | 0; + let y2 = ((clipbox.y_max / scratchUnitHeight + 0.5) * height) | 0; + let w = x2 - x; + let h = y2 - y; + if (flipY) { + y = ((-clipbox.y_max / scratchUnitHeight + 0.5) * height) | 0; + } + gl.scissor(x, y, w, h); + } else { + gl.disable(gl.SCISSOR_TEST); + } + switch (blendMode) { + case "additive": + gl.blendEquation(gl.FUNC_ADD); + gl.blendFunc(gl.ONE, gl.ONE); + break; + case "subtract": + gl.blendEquation(gl.FUNC_REVERSE_SUBTRACT); + gl.blendFunc(gl.ONE, gl.ONE); + break; + case "multiply": + gl.blendEquation(gl.FUNC_ADD); + gl.blendFunc(gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA); + break; + case "invert": + gl.blendEquation(gl.FUNC_ADD); + gl.blendFunc(gl.ONE_MINUS_DST_COLOR, gl.ONE_MINUS_SRC_COLOR); + break; + default: + gl.blendEquation(gl.FUNC_ADD); + gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); + } + } + + // Modifying and expanding Drawable + const gu = DrawableProto.getUniforms; + DrawableProto.getUniforms = function () { + if (active && toCorrectThing) { + setupModes(this.clipbox, this.blendMode, flipY); + } + return gu.call(this); + }; + DrawableProto.updateClipBox = function (clipbox) { + this.clipbox = clipbox; + }; + DrawableProto.updateBlendMode = function (blendMode) { + this.blendMode = blendMode; + }; + + // Expanding renderer + renderer.updateDrawableClipBox = function (drawableID, clipbox) { + const drawable = this._allDrawables[drawableID]; + if (!drawable) return; + drawable.updateClipBox(clipbox); + }; + renderer.updateDrawableBlendMode = function (drawableID, blendMode) { + const drawable = this._allDrawables[drawableID]; + if (!drawable) return; + drawable.updateBlendMode(blendMode); + }; + + // Reset on stop & clones inherit effects + const regTargetStuff = function (args) { + if (args.editingTarget) { + vm.removeListener("targetsUpdate", regTargetStuff); + const proto = vm.runtime.targets[0].__proto__; + const osa = proto.onStopAll; + proto.onStopAll = function () { + this.renderer.updateDrawableClipBox.call( + renderer, + this.drawableID, + null + ); + this.renderer.updateDrawableBlendMode.call( + renderer, + this.drawableID, + null + ); + osa.call(this); + }; + const mc = proto.makeClone; + proto.makeClone = function () { + const newTarget = mc.call(this); + if (this.clipbox || this.blendMode) { + newTarget.clipbox = Object.assign({}, this.clipbox); + newTarget.blendMode = this.blendMode; + renderer.updateDrawableClipBox.call( + renderer, + newTarget.drawableID, + this.clipbox + ); + renderer.updateDrawableBlendMode.call( + renderer, + newTarget.drawableID, + this.blendMode + ); + } + return newTarget; + }; + } + }; + vm.on("targetsUpdate", regTargetStuff); + + // Pen lines support + let emptyObject = {}; + let lastTarget = emptyObject; + let lastClipbox = {}; + let lastBlendMode = "default"; + function patchPen(skin) { + const ext_pen = runtime.ext_pen; + skin._lineOnBufferDrawRegionId.exit = () => { + skin._exitDrawLineOnBuffer(); + gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); + gl.disable(gl.SCISSOR_TEST); + lastTarget = emptyObject; + lastClipbox = null; + lastBlendMode = "default"; + }; + const willDrawPenWithTarget = function (target) { + if (!penDirty && target == lastTarget) return; + penDirty = false; + + const clipbox = target.clipbox; + if ( + !lastClipbox ^ !clipbox || + lastBlendMode != target.blendMode || + (clipbox && + (clipbox.x_min != lastClipbox.x_min || + clipbox.y_min != lastClipbox.y_min || + clipbox.x_max != lastClipbox.x_max || + clipbox.y_max != lastClipbox.y_max)) + ) { + if (skin.a_lineColorIndex) { + skin._flushLines(); + } + lastTarget = target; + if (clipbox) { + lastClipbox = { + x_min: clipbox.x_min, + y_min: clipbox.y_min, + x_max: clipbox.x_max, + y_max: clipbox.y_max, + }; + } else { + lastClipbox = null; + } + lastBlendMode = target.blendMode; + } + }; + // onTargetMoved function of pen draws a line. + // When drawing a line it is important to know the target. + // This saves target. + const onTargetMoved = ext_pen._onTargetMoved; + ext_pen._onTargetMoved = function (target, oldX, oldY, isForce) { + willDrawPenWithTarget(target); + onTargetMoved.call(this, target, oldX, oldY, isForce); + }; + // Existing tragets may still have old onTargetMoved + for (let target in runtime.tragets) { + if (target.onTargetMoved == onTargetMoved) { + target.onTargetMoved = ext_pen._onTargetMoved; + } + } + // When drawing a dot it is important to know the target. + // This saves target. + const penDown = ext_pen._penDown; + ext_pen._penDown = function (target) { + willDrawPenWithTarget(target); + penDown.call(this, target); + }; + // Set up correct clipping/blending before drawing + const flushLines = skin.__proto__._flushLines; + skin.__proto__._flushLines = function () { + setupModes(lastClipbox, lastBlendMode, true); + flushLines.call(this); + }; + } + if (renderer._allSkins[renderer._penSkinId]) { + // If pen skin already exists, things can be patched + patchPen(renderer._allSkins[renderer._penSkinId]); + } else { + // If pen skin does not exist, wait until it will, + // trigger code once, and return everything as it was + const createPenSkin = renderer.createPenSkin; + renderer.createPenSkin = function () { + let skinId = createPenSkin.call(this); + patchPen(renderer._allSkins[skinId]); + renderer.createPenSkin = createPenSkin; + return skinId; + }; + } + + class Extension { + getInfo() { + return { + id: "xeltallivclipblend", + name: "Clipping & Blending", + color1: "#9966FF", + color2: "#855CD6", + color3: "#774DCB", + menuIconURI: icon, + blocks: [ + { + opcode: "setClipbox", + blockType: Scratch.BlockType.COMMAND, + text: "set clipping box x1:[X1] y1:[Y1] x2:[X2] y2:[Y2]", + arguments: { + X1: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + Y1: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "0", + }, + X2: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "100", + }, + Y2: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: "100", + }, + }, + filter: [Scratch.TargetType.SPRITE], + extensions: ["colours_looks"], + }, + { + opcode: "clearClipbox", + blockType: Scratch.BlockType.COMMAND, + text: "clear clipping box", + filter: [Scratch.TargetType.SPRITE], + extensions: ["colours_looks"], + }, + { + opcode: "getClipbox", + blockType: Scratch.BlockType.REPORTER, + text: "clipping box [PROP]", + arguments: { + PROP: { + type: Scratch.ArgumentType.STRING, + defaultValue: "width", + menu: "props", + }, + }, + filter: [Scratch.TargetType.SPRITE], + extensions: ["colours_looks"], + }, + "---", + { + opcode: "setBlend", + blockType: Scratch.BlockType.COMMAND, + text: "use [BLENDMODE] blending ", + arguments: { + BLENDMODE: { + type: Scratch.ArgumentType.STRING, + defaultValue: "default", + menu: "blends", + }, + }, + filter: [Scratch.TargetType.SPRITE], + extensions: ["colours_looks"], + }, + { + opcode: "getBlend", + blockType: Scratch.BlockType.REPORTER, + text: "blending", + filter: [Scratch.TargetType.SPRITE], + disableMonitor: true, + extensions: ["colours_looks"], + }, + "---", + { + opcode: "setAdditiveBlend", + blockType: Scratch.BlockType.COMMAND, + text: "turn additive blending [STATE]", + arguments: { + STATE: { + type: Scratch.ArgumentType.STRING, + defaultValue: "on", + menu: "states", + }, + }, + filter: [Scratch.TargetType.SPRITE], + hideFromPalette: true, + extensions: ["colours_looks"], + }, + { + opcode: "getAdditiveBlend", + blockType: Scratch.BlockType.BOOLEAN, + text: "is additive blending on?", + filter: [Scratch.TargetType.SPRITE], + hideFromPalette: true, + disableMonitor: true, + extensions: ["colours_looks"], + }, + ], + menus: { + states: { + acceptReporters: true, + items: ["on", "off"], + }, + blends: { + acceptReporters: true, + items: ["default", "additive", "subtract", "multiply", "invert"], + }, + props: { + acceptReporters: true, + items: ["width", "height", "min x", "min y", "max x", "max y"], + }, + }, + }; + } + + setClipbox({ X1, Y1, X2, Y2 }, { target }) { + if (target.isStage) return; + const newClipbox = { + x_min: Math.min(X1, X2), + y_min: Math.min(Y1, Y2), + x_max: Math.max(X1, X2), + y_max: Math.max(Y1, Y2), + }; + penDirty = true; + target.clipbox = newClipbox; + renderer.updateDrawableClipBox.call( + renderer, + target.drawableID, + newClipbox + ); + if (target.visible) { + renderer.dirty = true; + target.emitVisualChange(); + target.runtime.requestRedraw(); + target.runtime.requestTargetsUpdate(target); + } + } + + clearClipbox(args, { target }) { + if (target.isStage) return; + target.clipbox = null; + penDirty = true; + renderer.updateDrawableClipBox.call(renderer, target.drawableID, null); + if (target.visible) { + renderer.dirty = true; + target.emitVisualChange(); + target.runtime.requestRedraw(); + target.runtime.requestTargetsUpdate(target); + } + } + + getClipbox({ PROP }, { target }) { + const clipbox = target.clipbox; + if (!clipbox) return ""; + switch (PROP) { + case "width": + return clipbox.x_max - clipbox.x_min; + case "height": + return clipbox.y_max - clipbox.y_min; + case "min x": + return clipbox.x_min; + case "min y": + return clipbox.y_min; + case "max x": + return clipbox.x_max; + case "max y": + return clipbox.y_max; + default: + return ""; + } + } + + setBlend({ BLENDMODE }, { target }) { + let newValue = null; + switch (BLENDMODE) { + case "default": + case "additive": + case "subtract": + case "multiply": + case "invert": + newValue = BLENDMODE; + break; + default: + return; + } + if (target.isStage) return; + penDirty = true; + target.blendMode = newValue; + renderer.updateDrawableBlendMode.call( + renderer, + target.drawableID, + newValue + ); + if (target.visible) { + renderer.dirty = true; + target.emitVisualChange(); + target.runtime.requestRedraw(); + target.runtime.requestTargetsUpdate(target); + } + } + + getBlend(args, { target }) { + return target.blendMode ?? "default"; + } + + setAdditiveBlend({ STATE }, util) { + if (STATE === "on") this.setBlend({ BLENDMODE: "additive" }, util); + if (STATE === "off") this.setBlend({ BLENDMODE: "default" }, util); + } + + getAdditiveBlend(args, { target }) { + return target.blendMode === "additive"; + } + } + + Scratch.extensions.register(new Extension()); +})(Scratch); + })(Scratch);