From c68bb50960f818fa526c6321ed9d670789932da7 Mon Sep 17 00:00:00 2001 From: Pedro Chavez <122649005+salagata@users.noreply.github.com> Date: Tue, 28 Jul 2026 01:36:23 -0500 Subject: [PATCH 1/9] Added Complex Numbers extension Adds my extension (salagata/reisen) Complex Numbers. Complex Number Type for do complex analysis functions, ~~better implementation than the one made by jwklong in Mathemathics extension lmao~~ --- static/extensions/salagata/reisenComplex.js | 1387 +++++++++++++++++++ 1 file changed, 1387 insertions(+) create mode 100644 static/extensions/salagata/reisenComplex.js diff --git a/static/extensions/salagata/reisenComplex.js b/static/extensions/salagata/reisenComplex.js new file mode 100644 index 000000000..07de04c6a --- /dev/null +++ b/static/extensions/salagata/reisenComplex.js @@ -0,0 +1,1387 @@ + +(function (Scratch) { + 'use strict'; + if (!Scratch.extensions.unsandboxed) { + throw new Error('somehow \'Complex Numbers\' must run unsandboxed'); + } + + // 1 + // -1 + // i + // -i + // 1 + i + // 1 - i + // -1 + i + // -1 - i + + // function parseStringToComplex(str) { + + // } + Scratch.translate.setup({ + es: { + "Complex Numbers": "Números Complejos", + "Complex Number Type for do complex analysis functions, better implementation than the one made by jwklong in Mathemathics extension lmao": + "El tipo de dato de Números complejos para realizar funciones de analisis complejo, mejor implementación que la que jwklong hizo en la extensión Mathemathics", + "complex number from [REAL]": "número complejo desde [REAL]", + "complex number from [IMAGINARY]i": "número complejo desde [IMAGINARY]i", + "complex number [REAL] + [IMAGINARY]i": "número complejo [REAL] + [IMAGINARY]i", + "complex number modulus: [R] phase: [PHASE]": "número complejo de módulo: [R] fase: [PHASE]", + "complex number modulus: 1 phase: [PHASE]": "número complejo de módulo: 1 fase [PHASE]", + "real part [A]": "parte real [A]", + "imaginary part [A]": "parte imaginaria [A]", + "absolute value [A]": "valor absoluto [A]", + "phase [A]": "fase [A]", + "conjugate [A]": "conjugado [A]", + "multiply [A] with its conjugate":"multiplicar [A] con su conjugado", + "reciprocal [A]": "recíproca [A]", + // "complex number in polar form modulus: [R] phase: [PHASE]": "número complejo en forma polar modulo: [R] fase [PHASE]", + "[POLAR] to rectangular form": "[POLAR] a forma rectangular", + "[COMPLEX] to polar form": "[COMPLEX] a forma polar", + "multiply [A] with [B] using the polar form": "multiplicar [A] con [B] usando la forma polar", + "divide [A] with [B] using the polar form": "dividir [A] con [B] usando la forma polar", + "[A] ^ [B] using the polar form": "[A] ^ [B] usando la forma polar", + "squareroot of [A]": "raíz cuadrada de [A]", + "[B]th root of [A] using the polar form": "[B]ésima raíz de [A] usando la forma polar", + "solutions of equation [A]x^2 + [B]x + [C] = 0": "soluciones de la ecuación [A]x^2 + [B]x + [C] = 0", + "[SOLUTION] solution of equation [A]x^2 + [B]x + [C] = 0": "[SOLUTION] solución de la ecuación [A]x^2 + [B]x + [C] = 0", + "positive": "positiva", + "negative": "negativa", + "roots of equation x^[A] - [B] = 0": "raices de la ecuación x^[A] - [B] = 0", + "roots of equation [C]x^[A] - [B] = 0": "raices de la ecuación [C]x^[A] - [B] = 0", + "[D]th root of equation x^[A] - [B] = 0": "[D]ava raíz de la ecuación x^[A] - [B] = 0", + "[D]th root of equation [C]x^[A] - [B] = 0": "[D]ava raíz de la ecuación [C]x^[A] - [B] = 0" + }, + }); + + function radianToDegrees(radian) { + return radian * (180 / Math.PI); + } + + function degreesToRadian(degree) { + return degree * (Math.PI / 180); + } + + function parseComplexToString(real,imaginary) { + let str = ""; + if(!real && !imaginary) { + return "0"; + } + if(real) { + // 1 + // -1 + str += String(real); + if(imaginary) { + if(imaginary > 0) { + str += "+"; + } + str += String(imaginary) + "i"; + } + } else { + // i + // -i + str += String(imaginary) + "i" + } + return str + } + + function roundToDigits(n,d) { + const dd = 10 ** d + return Math.floor(n * dd) / dd + } + + /** + * @param {number} x + * @returns {string} + */ + function formatNumber(x) { + if (x >= 1e6) { + return x.toExponential(4) + } else { + x = Math.floor(x * 1000) / 1000 + return x.toFixed(Math.min(3, (String(x).split('.')[1] || '').length)) + } + } + + function clampAngleDegrees(angle) { + + const s = Math.sign(angle); + const a = angle % 360 + + const b = (s === -1) ? + ((a <= -180) ? a + 360 : a) : + ((a > 180) ? a - 360 : a); + + return b; + } + + function clampAngleRadians(angle) { + let a = angle; + + while(a <= 0) { + a += Math.PI; + } + + return a + } + + function castAngle(angle) { + return String((Math.sign(angle) === -1) ? angle + 360 : angle); + } + + function transposeAngle(angle) { + return -angle + 90 + } + + function untransposeAngle(angle) { + return -(angle - 90) + } + + const Degrees = { + sin(x) { + return Math.sin(degreesToRadian(transposeAngle(x))); + }, + cos(x) { + return Math.cos(degreesToRadian(transposeAngle(x))); + }, + atan2(a,b) { + return radianToDegrees(Math.atan2(a,b)); + } + } + + // credits for many parts of this code to jwklong owo + + function span(text) { + let el = document.createElement('span') + el.innerHTML = text + el.style.display = 'hidden' + el.style.whiteSpace = 'nowrap' + el.style.width = '100%' + el.style.textAlign = 'center' + return el + } + + + /** + * A complex number + * + * @class ComplexNumberType + * @typedef {ComplexNumberType} + */ + class ComplexNumberType { + customId = "reisenComplexNumber"; + + + /** + * Creates an instance of ComplexNumberType. + * + * @constructor + * @param {number} [real=0] Real part + * @param {number} [imaginary=0] Imaginary part + * @param {number} [modulus] Modulus in case you're working with polars + * @param {number} [angle] Angle in case you're working with polars, in degrees + */ + constructor(real = 0,imaginary = 0, modulus, angle) { + // console.log(modulus, angle) + if(!(typeof modulus == "undefined" || typeof angle == "undefined")) { + this._fromPolar = true; + this._modulus = modulus; + this._angle = clampAngleDegrees(angle); + + switch (this._angle) { + case 360: + case 0: + this.real = 0 ; + this.imaginary = modulus; + break; + + case 90: + this.real = modulus; + this.imaginary = 0; + break; + + case 180: + this.real = 0; + this.imaginary = -modulus; + break; + + case 270: + this.real = -modulus; + this.imaginary = 0; + break; + + default: + + this.real = (isNaN(real) | real == 0) ? modulus * Degrees.cos(angle) : real; + this.imaginary = (isNaN(imaginary) | imaginary == 0) ? modulus * Degrees.sin(angle) : imaginary; + break; + } + } else { + this._fromPolar = false; + this._modulus = null; + this._angle = null; + + this.real = isNaN(real) ? 0 : real; + this.imaginary = isNaN(imaginary) ? 0 : imaginary; + } + } + + static toComplex(u) { + if (u instanceof ComplexNumberType) { + if(u._fromPolar) { + return new ComplexNumberType(u.real, u.imaginary, u.absolute, clampAngleDegrees(u.argument)); + } else { + return new ComplexNumberType(u.real, u.imaginary); + } + } + // if (u instanceof VectorType) return new ComplexNumberType(u.x, u.y); + if (u instanceof Array) { + if (u.length == 4) { + return new ComplexNumberType(u[0], u[1], u[2], u[3]) + } + + if (u.length == 2) { + return new ComplexNumberType(u[0], u[1]) + } + }; + if (typeof u == "number") { + return new ComplexNumberType(u); + } + if (String(u).split(',')) { + const s = String(u).split(','); + return new ComplexNumberType(Scratch.Cast.toNumber(s[0]), Scratch.Cast.toNumber(s[1])) + } + return new ComplexNumberType(0, 0) + } + + + /** + * Support for the Jwklong Array Handler! + * + * @returns {string} + */ + jwArrayHandler() { + return 'Complex' + } + + /** + * Casts the complex number as a string, choose whether to use rectangular or polar form + * + * @param {boolean} [polarForm=false] Choose whether to convert it as polar form when casting to string + * @returns {string} + */ + toString(polarForm = false) { + if(polarForm) { + return `${this.absolute}∠${this.argument}°`; + } else { + return parseComplexToString(this.real,this.imaginary) + } + } + + toMonitorContent = () => span(this.toString()) + + toReporterContent() { + let root = document.createElement('div') + root.textContent = parseComplexToString(this.real,this.imaginary); + return root + } + + /** + * Returns the absolute value or modulus of a complex number + * @returns {number} + */ + get absolute() { + if(this._fromPolar) { + return this._modulus + } else { + return Math.hypot(this.real, this.imaginary) + } + } + + /** + * Returns the argument or phase of a complex number in radians + * @returns {number} + */ + get argument() { + if(this._fromPolar) { + return this._angle + } else { + return Degrees.atan2(this.real, this.imaginary) + } + } + + /** @returns {ComplexNumberType} */ + get conjugate() { + return new ComplexNumberType(this.real,-this.imaginary) + } + + + toJSON() { + return { + real: this.real, + imaginary: this.imaginary + } + } + + toArray() { + return [ this.real, this.imaginary ] + } + + + /** + * Creates a complex number given it's polar form + * + * @static + * @param {number} absolute The absolute value, or modulus of the original number + * @param {number} argument The angle, in degrees + * @returns {ComplexNumberType} + */ + static fromPolar(absolute, argument) { + const real = absolute * Degrees.cos(argument) + const imaginary = absolute * Degrees.sin(argument) + return new ComplexNumberType(real, imaginary, absolute, clampAngleDegrees(argument)) + } + } + + const ComplexNumber = { + Type: ComplexNumberType, + Block: { + blockType: Scratch.BlockType.REPORTER, + blockShape: Scratch.BlockShape.BUMPED, + forceOutputType: "ComplexNumber", + disableMonitor: true + }, + + Argument: { + shape: Scratch.BlockShape.BUMPED, + check: [ "ComplexNumber" ] + }, + + /** + * Serializer for this type + * + * @param {ComplexNumberType} z Unserialized + * @returns {{}} + */ + Serializer(z) { + if(z._fromPolar) { + return [z.real, z.imaginary, z.absolute, z.argument]; + } else { + return [z.real, z.imaginary]; + } + }, + + + /** + * Deserializer for this type + * + * @param {[number,number]|[number,number,number,number]} z Serialized + * @returns {ComplexNumberType} + */ + Deserializer(z) { + if(z.length == 4) { + return new ComplexNumber.Type(z[0],z[1],z[2],z[3]) + } else { + return new ComplexNumber.Type(z[0],z[1]) + } + } + } + + class ComplexNumberExtension { + constructor() { + Scratch.vm.reisenComplexNumber = ComplexNumber, + // Scratch.vm.reisenComplexPolar = ComplexPolar, + Scratch.vm.runtime.registerSerializer( + "reisenComplexNumber", + ComplexNumber.Serializer, ComplexNumber.Deserializer + ) + + this.formatMessage = function (id) { + return Scratch.translate({ id: id, default: id }); + }; + + this.formatEveryBlock = function (blocks) { + // console.log("Before") + // console.log(blocks) + return blocks + // return blocks.map(block => { + // console.log("Loop") + // console.log(block) + // block.text = Scratch.translate({id: block.text, default: block.text}); + // console.log("Next") + // console.log(block.text) + // return block.text + // }) + } + } + + getInfo() { + return { + id: "reisenComplexNumber", + name: this.formatMessage("Complex Numbers"), + description: this.formatMessage("Complex Number Type for do complex analysis functions, better implementation than the one made by jwklong in Mathemathics extension lmao"), + color1: "#ffdd02", + blockText: "#000000", + blocks: [ + { + opcode: "realToComplex", + text: this.formatMessage("complex number from [REAL]"), + arguments: { + REAL: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1 + } + }, + ...ComplexNumber.Block + }, + { + opcode: "imaginaryToComplex", + text: this.formatMessage("complex number from [IMAGINARY]i"), + arguments: { + IMAGINARY: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1 + } + }, + ...ComplexNumber.Block + }, + { + opcode: "newComplex", + text: this.formatMessage("complex number [REAL] + [IMAGINARY]i"), + arguments: { + REAL: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1 + }, + IMAGINARY: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1 + } + }, + ...ComplexNumber.Block + }, + { + opcode: "newComplexFromPolar", + text: this.formatMessage("complex number modulus: [R] phase: [PHASE]"), + arguments: { + R: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1 + }, + PHASE: { + type: Scratch.ArgumentType.ANGLE, + defaultValue: 45 + } + }, + ...ComplexNumber.Block + }, + { + opcode: "newComplexFromPolar2", + text: this.formatMessage("complex number modulus: 1 phase: [PHASE]"), + arguments: { + PHASE: { + type: Scratch.ArgumentType.ANGLE, + defaultValue: 45 + } + }, + ...ComplexNumber.Block + }, + "---", + { + opcode: "getRealPart", + text: this.formatMessage("real part [A]"), + arguments: { + A: ComplexNumber.Argument + }, + blockType: Scratch.BlockType.REPORTER + }, + { + opcode: "getImaginaryPart", + text: this.formatMessage("imaginary part [A]"), + arguments: { + A: ComplexNumber.Argument + }, + blockType: Scratch.BlockType.REPORTER + }, + { + opcode: "getAbsolute", + text: this.formatMessage("absolute value [A]"), + arguments: { + A: ComplexNumber.Argument + }, + blockType: Scratch.BlockType.REPORTER + }, + { + opcode: "getArgument", + text: this.formatMessage("phase [A]"), + arguments: { + A: ComplexNumber.Argument + }, + blockType: Scratch.BlockType.REPORTER + }, + "---", + { + opcode: "add", + text: this.formatMessage("[A] + [B]"), + arguments: { + A: ComplexNumber.Argument, + B: ComplexNumber.Argument + }, + ...ComplexNumber.Block + }, + { + opcode: "subtract", + text: this.formatMessage("[A] - [B]"), + arguments: { + A: ComplexNumber.Argument, + B: ComplexNumber.Argument + }, + ...ComplexNumber.Block + }, + { + opcode: "multiply", + text: this.formatMessage("[A] x [B]"), + arguments: { + A: ComplexNumber.Argument, + B: ComplexNumber.Argument + }, + ...ComplexNumber.Block + }, + { + opcode: "divide", + text: this.formatMessage("[A] / [B]"), + arguments: { + A: ComplexNumber.Argument, + B: ComplexNumber.Argument + }, + ...ComplexNumber.Block + }, + "---", + { + opcode: "conjugate", + text: this.formatMessage("conjugate [A]"), + arguments: { + A: ComplexNumber.Argument + }, + ...ComplexNumber.Block + }, + { + opcode: "multiplyConjugate", + text: this.formatMessage("multiply [A] with its conjugate"), + arguments: { + A: ComplexNumber.Argument + }, + ...ComplexNumber.Block + }, + { + opcode: "reciprocal", + text: this.formatMessage("reciprocal [A]"), + arguments: { + A: ComplexNumber.Argument + }, + ...ComplexNumber.Block + }, + "---", + { + opcode: "polarToComplex", + text: this.formatMessage("[POLAR] to rectangular form"), + arguments: { + POLAR: ComplexNumber.Argument + }, + blockType: Scratch.BlockType.REPORTER + }, + { + opcode: "complexToPolar", + text: this.formatMessage("[COMPLEX] to polar form"), + arguments: { + COMPLEX: ComplexNumber.Argument + }, + blockType: Scratch.BlockType.REPORTER + }, + "---", + { + opcode: "multiply2", + text: this.formatMessage("multiply [A] with [B] using the polar form"), + arguments: { + A: ComplexNumber.Argument, + B: ComplexNumber.Argument + }, + ...ComplexNumber.Block + }, + { + opcode: "divide2", + text: this.formatMessage("divide [A] with [B] using the polar form"), + arguments: { + A: ComplexNumber.Argument, + B: ComplexNumber.Argument + }, + ...ComplexNumber.Block + }, + "---", + { + opcode: "power", + text: this.formatMessage("[A] ^ [B]"), + arguments: { + A: ComplexNumber.Argument, + B: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 2 + }, + }, + ...ComplexNumber.Block + }, + { + opcode: "squareRoot", + text: this.formatMessage("squareroot of [A]"), + arguments: { + A: ComplexNumber.Argument, + }, + ...ComplexNumber.Block + }, + { + opcode: "power2", + text: this.formatMessage("[A] ^ [B] using the polar form"), + arguments: { + A: ComplexNumber.Argument, + B: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 2 + }, + }, + ...ComplexNumber.Block + }, + { + opcode: "nRoot", + text: this.formatMessage("[B]th root of [A] using the polar form"), + arguments: { + A: ComplexNumber.Argument, + B: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 2 + }, + }, + ...ComplexNumber.Block + }, + "---", + { + opcode: "exponential", + text: this.formatMessage("e^[B]i pi"), + arguments: { + B: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1 + }, + }, + ...ComplexNumber.Block + }, + { + opcode: "exponential2", + text: this.formatMessage("e^[B]i"), + arguments: { + B: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1 + }, + }, + ...ComplexNumber.Block + }, + { + opcode: "naturalLogarithm", + text: this.formatMessage("ln [A]"), + arguments: { + A: ComplexNumber.Argument, + }, + ...ComplexNumber.Block + }, + // { + // opcode: "multiply", + // text: this.formatMessage("multiply [A] with [B] in polar form"), + // arguments: { + // A: ComplexNumber.Argument, + // B: ComplexNumber.Argument + // }, + // ...ComplexNumber.Block + // }, + { + opcode: "quadraticEquation", + text: this.formatMessage("solutions of equation [A]x^2 + [B]x + [C] = 0"), + arguments: { + A: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1 + }, + B: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 0 + }, + C: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1 + }, + }, + + blockType: Scratch.BlockType.REPORTER, + blockShape: Scratch.BlockShape.SQUARE, + disableMonitor: true + }, + { + opcode: "quadraticEquation2", + text: this.formatMessage("[SOLUTION] solution of equation [A]x^2 + [B]x + [C] = 0"), + arguments: { + A: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1 + }, + B: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 0 + }, + C: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1 + }, + SOLUTION: { + type: Scratch.ArgumentType.STRING, + menu: 'SOLUTIONS' + } + }, + ...ComplexNumber.Block + }, + { + opcode: "roots", + text: this.formatMessage("roots of equation x^[A] - [B] = 0"), + arguments: { + A: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1 + }, + B: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 0 + }, + }, + + blockType: Scratch.BlockType.REPORTER, + blockShape: Scratch.BlockShape.SQUARE, + disableMonitor: true + }, + { + opcode: "roots2", + text: this.formatMessage("roots of equation [C]x^[A] - [B] = 0"), + arguments: { + A: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1 + }, + B: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 0 + }, + C: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1 + }, + }, + + blockType: Scratch.BlockType.REPORTER, + blockShape: Scratch.BlockShape.SQUARE, + disableMonitor: true + }, + { + opcode: "roots3", + text: this.formatMessage("[D]th root of equation x^[A] - [B] = 0"), + arguments: { + A: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1 + }, + B: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 0 + }, + D: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1 + }, + }, + ...ComplexNumber.Block + }, + { + opcode: "roots4", + text: this.formatMessage("[D]th root of equation [C]x^[A] - [B] = 0"), + arguments: { + A: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1 + }, + B: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 0 + }, + C: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1 + }, + D: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1 + }, + }, + ...ComplexNumber.Block + }, + ], + menus: { + SOLUTIONS: { + acceptReporters: false, + items: [ + { + text: this.formatMessage("positive"), + value: "positive" + }, + { + text: this.formatMessage("negative"), + value: "negative" + }, + ] + } + } + } + } + + /** + * Creates a new complex number given the real and imaginary part + * + * @param { number } args.REAL + * @param { number } args.IMAGINARY + * @returns {ComplexNumberType} + */ + newComplex(args) { + const real = Scratch.Cast.toNumber(args.REAL) + const imaginary = Scratch.Cast.toNumber(args.IMAGINARY) + + return new ComplexNumberType(real,imaginary); + } + + + /** + * Given the polar form creates a complex + * + * @param { number } args.R + * @param { number } args.PHASE + * @returns {ComplexNumberType} + */ + newComplexFromPolar(args) { + const modulus = Scratch.Cast.toNumber(args.R) + const phase = Scratch.Cast.toNumber(args.PHASE); + + return ComplexNumberType.fromPolar(modulus,phase); + // z = r(cos a + i sin a) + // const real = modulus * Math.cos(phase); + // const imaginary = modulus * Math.sin(phase); + + } + + /** + * Given the phase creates a complex assuming modulus is 1 + * + * @param { number } args.PHASE + * @returns {ComplexNumberType} + */ + newComplexFromPolar2(args) { + const phase = Scratch.Cast.toNumber(args.PHASE); + + return ComplexNumberType.fromPolar(1,phase); + // z = r(cos a + i sin a) + // const real = modulus * Math.cos(phase); + // const imaginary = modulus * Math.sin(phase); + } + + + /** + * Converts a single real number into a complex type + * + * @param { number } args.REAL + * @returns {ComplexNumberType} + */ + realToComplex(args) { + return new ComplexNumberType(args.REAL,0) + } + + /** + * Converts a sole imaginary number into a complex type + * + * @param { number } args.IMAGINARY + * @returns {ComplexNumberType} + */ + imaginaryToComplex(args) { + return new ComplexNumberType(0,args.IMAGINARY) + } + + getRealPart(args) { + return ComplexNumberType.toComplex(args.A).real + } + + getImaginaryPart(args) { + return ComplexNumberType.toComplex(args.A).imaginary + } + + getAbsolute(args) { + return ComplexNumberType.toComplex(args.A).absolute + } + + getArgument(args) { + return ComplexNumberType.toComplex(args.A).argument + } + + conjugate(args) { + return ComplexNumberType.toComplex(args.A).conjugate + } + + add(args) { + const A = ComplexNumberType.toComplex(args.A); + const B = ComplexNumberType.toComplex(args.B); + + return new ComplexNumberType(A.real + B.real, A.imaginary + B.imaginary); + } + + subtract(args) { + const A = ComplexNumberType.toComplex(args.A); + const B = ComplexNumberType.toComplex(args.B); + + return new ComplexNumberType(A.real - B.real, A.imaginary - B.imaginary); + } + + multiply(args) { + const A = ComplexNumberType.toComplex(args.A); + const B = ComplexNumberType.toComplex(args.B); + + return new ComplexNumberType( + A.real * B.real - A.imaginary * B.imaginary, + A.real * B.imaginary + A.imaginary * B.real + ); + } + + multiplyConjugate(args) { + const A = ComplexNumberType.toComplex(args.A); + + return new ComplexNumberType(A.real ** 2 + A.imaginary ** 2, 0); + } + + reciprocal(args) { + const A = ComplexNumberType.toComplex(args.A); + const u = A.real ** 2 + A.imaginary ** 2; + + + return new ComplexNumberType(A.real / u, -A.imaginary / u); + } + + divide(args) { + const A = ComplexNumberType.toComplex(args.A); + const B = ComplexNumberType.toComplex(args.B); + const u = B.real ** 2 + B.imaginary ** 2; + + + + return new ComplexNumberType( + (A.real * B.real + A.imaginary * B.imaginary) / u, + (A.imaginary * B.real - A.real * B.imaginary) / u + ); + } + + + /** + * Converts a polar number into a complex number + * + * @param {ComplexNumberType} args.POLAR + * @returns {ComplexNumberType} + */ + polarToComplex(args) { + const POLAR = ComplexNumberType.toComplex(args.POLAR); + + return POLAR.toString(); + } + + + complexToPolar(args) { + const COMPLEX = ComplexNumberType.toComplex(args.COMPLEX); + + return COMPLEX.toString(true); + } + + + multiply2(args) { + const A = ComplexNumberType.toComplex(args.A); + const B = ComplexNumberType.toComplex(args.B); + + return new ComplexNumberType( + A.real * B.real - A.imaginary * B.imaginary, + A.real * B.imaginary + A.imaginary * B.real + , A.absolute * B.absolute + , untransposeAngle(-(A.argument + B.argument) + 180) + ); + } + + divide2(args) { + const A = ComplexNumberType.toComplex(args.A); + const B = ComplexNumberType.toComplex(args.B); + const u = B.real ** 2 + B.imaginary ** 2; + + return new ComplexNumberType( + (A.real * B.real + A.imaginary * B.imaginary) / u, + (A.imaginary * B.real - A.real * B.imaginary) / u + , A.absolute / B.absolute + , untransposeAngle(-(A.argument - B.argument)) + ); + } + + power(args) { + const A = ComplexNumberType.toComplex(args.A); + const power = Math.round(Scratch.Cast.toNumber(args.B)); + + const firstReal = A.real, firstImaginary = A.imaginary; + let pair = [firstReal, firstImaginary]; + + if(power == 0) { + return new ComplexNumberType(1,0) + } + if(power == 1) { + return A + } + if(power == -1) { + const u = A.real ** 2 + A.imaginary ** 2; + return new ComplexNumberType(A.real / u, -A.imaginary / u); + } + + const absPower = Math.abs(power); + + for (let _ = 1; _ < absPower; _++) { + pair = [ + pair[0] * firstReal - pair[1] * firstImaginary, + pair[0] * firstImaginary + pair[1] * firstReal + ]; + } + + if(power < -1) { + const u = pair[0] ** 2 + pair[1] ** 2; + return new ComplexNumberType(pair[0] / u, -pair[1] / u); + + } + + return new ComplexNumberType( + pair[0], pair[1] + ); + } + + + squareRoot(args) { + const A = ComplexNumberType.toComplex(args.A); + const r = Math.hypot(A.real, A.imaginary); + + return new ComplexNumberType( + Math.sqrt(1/2 * (r + A.real)), + (A.imaginary >= 0 ? 1 : -1) * Math.sqrt(1/2 * (r - A.real)), + ); + } + + power2(args) { + const A = ComplexNumberType.toComplex(args.A); + const power = Scratch.Cast.toNumber(args.B); + + if(power == 0) { + return new ComplexNumberType(1,0) + } + if(power == 1) { + return A + } + + const r = A.absolute ** power; + const phi = A.argument * power; + + return new ComplexNumberType( + r * Degrees.cos(phi), + r * Degrees.sin(phi), + r, phi + ); + } + + nRoot(args) { + const A = ComplexNumberType.toComplex(args.A); + const subRadical = Scratch.Cast.toNumber(args.B); + + if(subRadical == 0) { + return Infinity + } + if(subRadical == 1) { + return A + } + + const r = subRadical == 2 ? Math.sqrt(A.absolute) : (A.absolute ** (1/subRadical)); + const phi = A.argument / subRadical; + + return new ComplexNumberType( + r * Degrees.cos(phi), + r * Degrees.sin(phi), + r, phi + ); + } + + // power3(args) { + // const A = ComplexNumberType.toComplex(args.A); + // const power = Math.round(Scratch.Cast.toNumber(args.B)); + + // if(power == 0) { + // return new ComplexNumberType(1,0) + // } + // if(power == 1) { + // return A + // } + + // const r = A.absolute ** n; + // const phi = A.argument * n; + + // return new ComplexNumberType( + // undefined, undefined, + // r * Degrees.cos(phi), + // r * Degrees.sin(phi) + // ); + // } + exponential(args) { + const radians = Scratch.Cast.toNumber(args.B); + if(radians == 0) { + return new ComplexNumberType(1,0); + } + if(radians == 1) { + return new ComplexNumberType(-1,0,); + } + + const real = Math.cos(radians * Math.PI); + const imaginary = Math.sin(radians * Math.PI); + + return new ComplexNumberType(real, imaginary, 1, radianToDegrees(radians * Math.PI)) + } + exponential2(args) { + const radians = Scratch.Cast.toNumber(args.B); + if(radians == 0) { + return new ComplexNumberType(1,0); + } + if(radians == Math.PI) { + return new ComplexNumberType(-1,0); + } + + const real = Math.cos(radians); + const imaginary = Math.sin(radians); + + return new ComplexNumberType(real, imaginary, 1, radianToDegrees(radians)) + } + naturalLogarithm(args) { + const A = ComplexNumberType.toComplex(args.A); + + if(A.real == 0 && A.imaginary == 0) { + return NaN; + } + if(A.imaginary == 0) { + if(A.real > 0) { + return new ComplexNumberType(Math.log(A.real)); + } else { + return new ComplexNumberType(Math.log(Math.abs(A.real)),Math.PI); + } + } + if(A.real == 0) { + if(A.imaginary > 0) { + return new ComplexNumberType(Math.log(A.imaginary), Math.PI / 2); + } else { + return new ComplexNumberType(Math.log(Math.abs(A.imaginary)), -Math.PI / 2); + } + } + + const r = Math.log(A.absolute); + const arg = A.argument * Math.PI; + + return new ComplexNumberType(r,arg); + } + + quadraticEquation(args) { + const a = Math.round(Scratch.Cast.toNumber(args.A)); + const b = Math.round(Scratch.Cast.toNumber(args.B)); + const c = Math.round(Scratch.Cast.toNumber(args.C)); + + if(a == 0) { + throw new Error("Quadratic component can't be 0"); + } + + const det = b ** 2 - 4 * a * c; + let solutions = []; + + if(det > 0) { + const positive = ( -b + Math.sqrt(det)) / (2 * a); + const negative = ( -b - Math.sqrt(det)) / (2 * a); + + solutions = [ new ComplexNumberType(positive,0), new ComplexNumberType(negative,0) ]; + } + + if(det == 0) { + const unique = ( -b ) / (2 * a); + + solutions = [ new ComplexNumberType(unique,0), new ComplexNumberType(unique,0) ]; + } + + if(det < 0) { + const positive = new ComplexNumberType(-b / (2 * a), Math.sqrt(Math.abs(det)) / (2 * a)); + const negative = new ComplexNumberType(-b / (2 * a), -Math.sqrt(Math.abs(det)) / (2 * a)); + + solutions = [ positive, negative ]; + } + + return solutions; + } + quadraticEquation2(args) { + const a = Math.round(Scratch.Cast.toNumber(args.A)); + const b = Math.round(Scratch.Cast.toNumber(args.B)); + const c = Math.round(Scratch.Cast.toNumber(args.C)); + + + const det = b ** 2 - 4 * a * c; + let solutions = []; + + if(det > 0) { + const positive = ( -b + Math.sqrt(det)) / (2 * a); + const negative = ( -b - Math.sqrt(det)) / (2 * a); + + solutions = [ new ComplexNumberType(positive,0), new ComplexNumberType(negative,0) ]; + } + + if(det == 0) { + const unique = ( -b ) / (2 * a); + + solutions = [ new ComplexNumberType(unique,0), new ComplexNumberType(unique,0) ]; + } + + if(det < 0) { + const positive = new ComplexNumberType(-b / (2 * a), Math.sqrt(Math.abs(det)) / (2 * a)); + const negative = new ComplexNumberType(-b / (2 * a), Math.sqrt(Math.abs(det)) / (2 * a)); + + solutions = [ positive, negative ]; + } + + switch (args.SOLUTION) { + case "positive": + return solutions[0]; + + case "negative": + return solutions[1]; + + default: + break; + } + } + + roots(args) { + const a = Math.round(Math.abs(Scratch.Cast.toNumber(args.A))); + const b = Scratch.Cast.toNumber(args.B); + + if(a == 0) { + return NaN; + } + if(a == 1) { + return [ ComplexNumberType.toComplex(a) ]; + } + + let roots = []; + + const r = a == 2 ? Math.sqrt(b) : (b ** (1/a)); + for (let k = 0; k < a; k++) { + + const phi = (0 + k * 360) / a; + + roots.push(new ComplexNumberType( + r * Degrees.cos(phi), + r * Degrees.sin(phi), + r, phi + )); + } + + return roots; + } + + roots2(args) { + const a = Math.round(Math.abs(Scratch.Cast.toNumber(args.A))); + const b = Scratch.Cast.toNumber(args.B); + const c = Scratch.Cast.toNumber(args.C); + + if(a == 0) { + return NaN; + } + if(a == 1) { + return [ ComplexNumberType.toComplex(a) ]; + } + + let roots = []; + + const r = a == 2 ? Math.sqrt(b/c) : ((b/c) ** (1/a)); + for (let k = 0; k < a; k++) { + + const phi = (0 + k * 360) / a; + + roots.push(new ComplexNumberType( + r * Degrees.cos(phi), + r * Degrees.sin(phi), + r, phi + )); + } + + return roots; + } + + roots3(args) { + const a = Math.round(Math.abs(Scratch.Cast.toNumber(args.A))); + const b = Scratch.Cast.toNumber(args.B); + + const c = Scratch.Cast.toNumber(args.C); + + if(a == 0) { + return NaN; + } + if(a == 1) { + return [ ComplexNumberType.toComplex(a) ]; + } + + let roots = []; + + const r = a == 2 ? Math.sqrt(b) : (b ** (1/a)); + + const phi = (0 + d * 360) / a; + + return new ComplexNumberType( + r * Degrees.cos(phi), + r * Degrees.sin(phi), + r, phi + ); + } + + roots4(args) { + const a = Math.round(Math.abs(Scratch.Cast.toNumber(args.A))); + const b = Scratch.Cast.toNumber(args.B); + + const c = Scratch.Cast.toNumber(args.C); + + if(a == 0) { + return NaN; + } + if(a == 1) { + return [ ComplexNumberType.toComplex(a) ]; + } + + let roots = []; + + const r = a == 2 ? Math.sqrt(b/c) : ((b/c) ** (1/a)); + + const phi = (0 + d * 360) / a; + + return new ComplexNumberType( + r * Degrees.cos(phi), + r * Degrees.sin(phi), + r, phi + ); + } + } + Scratch.extensions.register( new ComplexNumberExtension() ) +})(Scratch) From 1db3c723eb8e0e8f39917726e8b52e46d53236e5 Mon Sep 17 00:00:00 2001 From: Pedro Chavez <122649005+salagata@users.noreply.github.com> Date: Tue, 28 Jul 2026 01:43:28 -0500 Subject: [PATCH 2/9] Add Complex Numbers extension with metadata Added a new entry for 'Complex Numbers' with detailed metadata. --- src/lib/extensions.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/lib/extensions.js b/src/lib/extensions.js index a4239a679..ae1da20ed 100644 --- a/src/lib/extensions.js +++ b/src/lib/extensions.js @@ -670,6 +670,17 @@ export default [ tags: ["customtype","data","utility","new","large"], creatorAlias: "AndrewGaming587" }, + { + name: "Complex Numbers", + description: "Complex Number Type for do complex analysis functions, better implementation than the one made by jwklong in Mathemathics extension lmao.", + code: "salagata/reisenComplex.js", + creator: "salagata", + tags: ["complex", "math", "graphics"], + creatorAlias: "Reisen", + notes: "Additional help by jwklong extensions", + unstable: false, + isGitHub: true, + }, { name: "Black Mold", From ca51432715b4e00d38ee8a5a44aab7be29707435 Mon Sep 17 00:00:00 2001 From: Pedro Chavez <122649005+salagata@users.noreply.github.com> Date: Tue, 28 Jul 2026 10:44:36 -0500 Subject: [PATCH 3/9] Add reisenComplex_placeholder.svg image file --- static/images/salagata/reisenComplex_placeholder.svg | 1 + 1 file changed, 1 insertion(+) create mode 100644 static/images/salagata/reisenComplex_placeholder.svg diff --git a/static/images/salagata/reisenComplex_placeholder.svg b/static/images/salagata/reisenComplex_placeholder.svg new file mode 100644 index 000000000..641dd1b98 --- /dev/null +++ b/static/images/salagata/reisenComplex_placeholder.svg @@ -0,0 +1 @@ +ie = cos(x)+isin(x)ixax +bx+c=0x - a=0n2 From 21cc1b0d216496b833b8e459dfc989d21db036f9 Mon Sep 17 00:00:00 2001 From: Pedro Chavez <122649005+salagata@users.noreply.github.com> Date: Tue, 28 Jul 2026 11:18:04 -0500 Subject: [PATCH 4/9] Modified Complex Numbers extension metadata and added the thumbnail Added the new extension for complex numbers with improved implementation and additional metadata. --- src/lib/extensions.js | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/lib/extensions.js b/src/lib/extensions.js index ae1da20ed..34770bba5 100644 --- a/src/lib/extensions.js +++ b/src/lib/extensions.js @@ -321,6 +321,18 @@ export default [ isGitHub: true, tags: ["new", "effects", "control", "data", "utility"] }, + { + name: "Complex Numbers", + description: "Complex Number Type for do complex analysis functions, better implementation than the one made by jwklong in Mathemathics extension lmao.", + code: "salagata/reisenComplex.js", + banner: "salagata/reisenComplex_placeholder.svg" + creator: "salagata", + tags: ["new","complex", "math", "graphics", "customtype", "utility"], + creatorAlias: "Reisen the Inaba", + notes: "Additional help by jwklong extensions", + unstable: false, + isGitHub: true, + }, { name: "Blobs", description: "An extension made for handling blobs which can be used to store files. Allows to easily make blobs for the Js extension", @@ -670,17 +682,6 @@ export default [ tags: ["customtype","data","utility","new","large"], creatorAlias: "AndrewGaming587" }, - { - name: "Complex Numbers", - description: "Complex Number Type for do complex analysis functions, better implementation than the one made by jwklong in Mathemathics extension lmao.", - code: "salagata/reisenComplex.js", - creator: "salagata", - tags: ["complex", "math", "graphics"], - creatorAlias: "Reisen", - notes: "Additional help by jwklong extensions", - unstable: false, - isGitHub: true, - }, { name: "Black Mold", From 76e7fa72fabc7ea0346201ec87a90cd7b808ea96 Mon Sep 17 00:00:00 2001 From: Pedro Chavez <122649005+salagata@users.noreply.github.com> Date: Tue, 28 Jul 2026 11:19:50 -0500 Subject: [PATCH 5/9] Minor error fixed --- src/lib/extensions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/extensions.js b/src/lib/extensions.js index 34770bba5..1636f7d8e 100644 --- a/src/lib/extensions.js +++ b/src/lib/extensions.js @@ -325,7 +325,7 @@ export default [ name: "Complex Numbers", description: "Complex Number Type for do complex analysis functions, better implementation than the one made by jwklong in Mathemathics extension lmao.", code: "salagata/reisenComplex.js", - banner: "salagata/reisenComplex_placeholder.svg" + banner: "salagata/reisenComplex_placeholder.svg", creator: "salagata", tags: ["new","complex", "math", "graphics", "customtype", "utility"], creatorAlias: "Reisen the Inaba", From caa89b92187083d20dcd159f276d6c76b1a4adde Mon Sep 17 00:00:00 2001 From: salagata <122649005+salagata@users.noreply.github.com> Date: Thu, 3 Sep 2026 22:40:18 -0500 Subject: [PATCH 6/9] Added docs and updated the extension a lot --- src/lib/Documentation/SalagataComplex.md | 77 + src/lib/Documentation/pages.js | 7 +- src/lib/extensions.js | 24 +- static/extensions/salagata/complex.js | 1943 +++++++++++++++++ static/extensions/salagata/reisenComplex.js | 1387 ------------ .../salagata/complexDocs/angleEquivalency.png | Bin 0 -> 43266 bytes .../complexDocs/complexPlaneAngles.png | Bin 0 -> 13316 bytes .../salagata/complexDocs/scratchAngles.png | Bin 0 -> 11355 bytes .../images/salagata/complex_placeholder.svg | 1 + .../salagata/reisenComplex_placeholder.svg | 1 - 10 files changed, 2039 insertions(+), 1401 deletions(-) create mode 100644 src/lib/Documentation/SalagataComplex.md create mode 100644 static/extensions/salagata/complex.js delete mode 100644 static/extensions/salagata/reisenComplex.js create mode 100644 static/images/salagata/complexDocs/angleEquivalency.png create mode 100644 static/images/salagata/complexDocs/complexPlaneAngles.png create mode 100644 static/images/salagata/complexDocs/scratchAngles.png create mode 100644 static/images/salagata/complex_placeholder.svg delete mode 100644 static/images/salagata/reisenComplex_placeholder.svg diff --git a/src/lib/Documentation/SalagataComplex.md b/src/lib/Documentation/SalagataComplex.md new file mode 100644 index 000000000..fedc55068 --- /dev/null +++ b/src/lib/Documentation/SalagataComplex.md @@ -0,0 +1,77 @@ +# Complex Numbers + +--- + +[Complex Numbers](https://en.wikipedia.org/wiki/Complex_number) is an extension that allows you to use Complex Numbers in Scratch, by introducing a new type: `ComplexNumber`. +These Complex Numbers can be used in, for example: +- Computer Graphics: Used for do transforms, simulations like the design of fractals(like the Mandelbrot Set) and rendering images. +- Video games and 2D/3D graphics: Rotations, Scaling, Translation, Reflection. +- Signal processing and signal integration: like in FFT and the Frequency spectrum. +- Cryptography and Computer Security. + +These Complex numbers have the property to choose the representation you want to use. Either the polar form or rectangular form + +--- + +## Angles + +While developing this extension, I found a problem in the way that Scratch represents the Angles (Internally, and in ANGLE inputs). +![Representation of angles in Scratch](https://files.catbox.moe/7uz5t7.png) +The angles basically start in 0° but 0° is the top (when it should be in the right), then the front is 90°(when it should be in the top), which it's confusing. +The trigonometrical functions (even the ones defined in ([sin v] of ()) block), recieve as argument a different kind of angle. +Internally, Scratch has to do `90 - SCRATCH_ANGLE` in order to perform any trigonometrical operation. It converts the Scratch angle recieved commonly as input into a Plane angle(The one used in Mathematics). +![Complex Plane angles](https://files.catbox.moe/umf5i8.png) +In this documentation. I'm going to define `Scratch Angle` as the angles that are used in Scratch angle inputs, and `Complex Plane angle` as the angles that are used in Trigonometry (and Complex Analysis). And `to transform ANGLE into ANGLE` to the mathematical operation of converting one kind of angle into another, and viceversa. +```scratch +set direction to (90) ::motion // Arguments are in Scratch angles +direction ::motion reporter // Returns in Scratch angles +[sin v] of (60) ::operators reporter // Arguments are in Complex Plane angles +``` + +In general. +``` +COMPLEX_PLANE_ANGLE = 90 - SCRATCH_ANGLE +SCRATCH_ANGLE = -COMPLEX_PLANE_ANGLE + 90 +``` + +JwVector doesn't solve the problem, it just abstracts the vector functions for use only Scratch angles (in addition of casting degrees to radians and floating-point precision errors). + +```scratch +new vector magnitude: (1) angle: (0) ::#6BABFF reporter // Recieves the angle in Scratch angles +angle of () ::#6BABFF reporter // Returns the answer in Scratch angles +``` + +This point is important because Scratch Angles doesn't follow the natural properties that Complex Plane angles do, like +*"The angle of the product of a complex number of modulus 1 and angle `ANGLE_1` with a complex number of modulus 1 and angle `ANGLE_2` is equal to `ANGLE_1 + ANGLE_2`"*(One of the properties of complex numbers in polar form) is **not** true if the angles are defined in Scratch angles. Or, ilustrated. +![Angle equivalency](https://files.catbox.moe/fz14md.png) + +This extension interally expresses the angles of Complex Numbers in Complex Plane Angles and recieves/returns Scratch angles for deal with this problem. In addition of the following blocks. + +```scratch +transform (90) into Complex Plane Angle ::operators reporter // Trasnsforms Scratch angles into Complex Plane Angle, this also has the Angle input +transform (0) into Scratch Angle ::operators reporter // Trasnsforms Complex Plane Angle into Scratch angles +``` + +This might look confusing(because it is), so, stay with the idea that Scratch uses a different kind of angles that doesn't follow the same properties as the normal angles. And you just need to "rotate and invert them" to convert one into another +## Internal Representation + +The `ComplexNumberType` in this extension has 2 representations. Rectangular and Polar. +At the moment of creating a complex number in polar form, it is created + +### Rectangular representation + +| Property | Description | +| ------------- |:-------------:| +| real | Real part | +| imaginary | Imaginary Part | + +### Polar representation + +| Property | Description | +| ------------- |:-------------:| +| real | Real part | +| imaginary | Imaginary Part | +| modulus | Modulus of the angle | +| phase | The complex phase transfomed into a *complex plane angle* | + +NOTE: The bumped inputs cannot be displayed here, and we have turned them into circular inputs. \ No newline at end of file diff --git a/src/lib/Documentation/pages.js b/src/lib/Documentation/pages.js index 546137a0f..d40d8da95 100644 --- a/src/lib/Documentation/pages.js +++ b/src/lib/Documentation/pages.js @@ -42,6 +42,8 @@ import ProjectInterfaces from "./ProjectInterfaces.md?raw"; // Date Format V2 import DateFormatV2 from "./DateFormatV2.md?raw"; +import PageComplexNumbers from "./SalagataComplex.md?raw"; + export default { // the key is the path to the docs page // so you can do "sharkpool-particle-tools" for example @@ -81,5 +83,8 @@ export default { // Project Interfaces "ProjectInterfaces": ProjectInterfaces, - "DateFormatV2": DateFormatV2 + "DateFormatV2": DateFormatV2, + + // Complex Numebrs + "SalagataComplex": PageComplexNumbers }; diff --git a/src/lib/extensions.js b/src/lib/extensions.js index 745f98e7c..58b2fd447 100644 --- a/src/lib/extensions.js +++ b/src/lib/extensions.js @@ -321,6 +321,18 @@ export default [ isGitHub: true, tags: ["effects", "control", "data", "utility"] }, + { + name: "Complex Numbers", + description: "Complex Number Type for do complex analysis functions. Ideal for things were rotation is involved like bullet hells or rendering.", + code: "salagata/complex.js", + banner: "salagata/complex_placeholder.svg", + documentation: "SalagataComplex", + creator: "salagata", + tags: ["new","complex", "math", "graphics", "customtype", "utility"], + creatorAlias: "Reisen the Inaba", + notes: "Additional help by jwklong extensions", + isGitHub: true, + }, { name: "3D Vectors & Quaternions", description: "Perform 3D Math and Rotations with Vectors and Quaternions", @@ -331,18 +343,6 @@ export default [ isGitHub: true, tags: ["new", "customtype", "3D", "data", "utility", "math"] }, - { - name: "Complex Numbers", - description: "Complex Number Type for do complex analysis functions, better implementation than the one made by jwklong in Mathemathics extension lmao.", - code: "salagata/reisenComplex.js", - banner: "salagata/reisenComplex_placeholder.svg", - creator: "salagata", - tags: ["new","complex", "math", "graphics", "customtype", "utility"], - creatorAlias: "Reisen the Inaba", - notes: "Additional help by jwklong extensions", - unstable: false, - isGitHub: true, - }, { name: "Blobs", description: "An extension made for handling blobs which can be used to store files. Allows to easily make blobs for the Js extension", diff --git a/static/extensions/salagata/complex.js b/static/extensions/salagata/complex.js new file mode 100644 index 000000000..b4327e338 --- /dev/null +++ b/static/extensions/salagata/complex.js @@ -0,0 +1,1943 @@ +(function (Scratch) { + 'use strict'; + if (!Scratch.extensions.unsandboxed) { + throw new Error('somehow \'Complex Numbers\' must run unsandboxed'); + } + + + // 1 + // -1 + // i + // -i + // 1 + i + // 1 - i + // -1 + i + // -1 - i + + // function parseStringToComplex(str) { + + // } + Scratch.translate.setup({ + es: { + "Complex Numbers": "Números Complejos", + "Complex Number Type for do complex analysis functions, perfect for rotation where vectors are slow": + "El tipo de dato de Números complejos para realizar funciones de analisis complejo, perfecto para rotaciones donde los vectores son lentos", + "complex number from [REAL]": "número complejo desde [REAL]", + "complex number from [IMAGINARY]i": "número complejo desde [IMAGINARY]i", + "complex number [REAL] + [IMAGINARY]i": "número complejo [REAL] + [IMAGINARY]i", + "complex number modulus: [R] phase: [PHASE]": "número complejo de módulo: [R] fase: [PHASE]", + "complex number modulus: 1 phase: [PHASE]": "número complejo de módulo: 1 fase [PHASE]", + "real part [A]": "parte real [A]", + "imaginary part [A]": "parte imaginaria [A]", + "absolute value [A]": "valor absoluto [A]", + "phase [A]": "fase [A]", + "conjugate [A]": "conjugado [A]", + "[A] x [B] using [FORM]": "[A] x [B] usando [FORM]", + "[A] / [B] using [FORM]": "[A] / [B] usando [FORM]", + "[A] ^ [B] using [FORM]": "[A] ^ [B] usando [FORM]", + "multiply [A] with its conjugate":"multiplicar [A] con su conjugado", + "reciprocal [A]": "recíproca [A]", + "parse [A] to a complex number": "convertir [A] a un número complejo", + // "complex number in polar form modulus: [R] phase: [PHASE]": "número complejo en forma polar modulo: [R] fase [PHASE]", + "[COMPLEX] to [FORM] as text": "[COMPLEX] a [FORM] como texto", + "use [FORM] for [COMPLEX]": "usar [FORM] para [COMPLEX]", + // "multiply [A] with [B] using the polar form": "multiplicar [A] con [B] usando la forma polar", + // "divide [A] with [B] using the polar form": "dividir [A] con [B] usando la forma polar", + "[A] ^ [B] using the polar form": "[A] ^ [B] usando la forma polar", + "square root of [A]": "raíz cuadrada de [A]", + "[B]th root of [A] using the polar form": "[B]ésima raíz de [A] usando la forma polar", + "solutions of equation [A]x^2 + [B]x + [C] = 0": "soluciones de la ecuación [A]x^2 + [B]x + [C] = 0", + "[SOLUTION] solution of equation [A]x^2 + [B]x + [C] = 0": "[SOLUTION] solución de la ecuación [A]x^2 + [B]x + [C] = 0", + "positive": "positiva", + "negative": "negativa", + "first": "primera", + "second": "segunda", + "polar form": "forma polar", + "rectangular form": "forma rectangular", + // "roots of equation x^[A] - [B] = 0": "raices de la ecuación x^[A] - [B] = 0", + "roots of equation [C]x^[A] - [B] = 0": "raices de la ecuación [C]x^[A] - [B] = 0", + // "[D]th root of equation x^[A] - [B] = 0": "[D]ava raíz de la ecuación x^[A] - [B] = 0", + "[D]th root of equation [C]x^[A] - [B] = 0": "[D]ava raíz de la ecuación [C]x^[A] - [B] = 0", + 'position in [FORM]': "posición en [FORM]", + 'go to [COMPLEX] using [FORM]': "ir a [COMPLEX] usando [FORM]", + 'direction in [FORM]': "dirección en [FORM]", + 'point in sense of [COMPLEX] using [FORM]': "apuntar en sentido de [COMPLEX] usando [FORM]", + 'stretch in [FORM]': "estiramiento en [FORM]", + 'set stretch to [COMPLEX] using [FORM]': "establecer estiramiento en [COMPLEX] usando [FORM]", + 'mouse position in [FORM]': "posición del ratión en [FORM]", + "convert [COMPLEX] to vector": "convertir [COMPLEX] a vector", + "convert [VECTOR] to complex number": "convertir [VECTOR] a número complejo", + "transform [ANGLE] into Complex Plane Angle": "transformar [ANGLE] en ángulo del plano complejo", + "transform [ANGLE] into Scratch Angle": "transformar [ANGLE] en ángulo de Scratch", + }, + }); + + function radianToDegrees(radian) { + return radian * (180 / Math.PI); + } + + function degreesToRadian(degree) { + return degree * (Math.PI / 180); + } + + function parseComplexToString(real,imaginary) { + let str = ""; + if(!real && !imaginary) { + return "0"; + } + if(real) { + // 1 + // -1 + str += String(real); + if(imaginary) { + if(imaginary > 0) { + str += "+"; + } + str += String(imaginary) + "i"; + } + } else { + // i + // -i + str += String(imaginary) + "i" + } + return str + } + + function roundToDigits(n,d) { + const dd = 10 ** d + return Math.floor(n * dd) / dd + } + + /** + * @param {number} x + * @returns {string} + */ + function formatNumber(x) { + if (x >= 1e6) { + return x.toExponential(4) + } else { + x = Math.floor(x * 1000) / 1000 + return x.toFixed(Math.min(3, (String(x).split('.')[1] || '').length)) + } + } + + function clampAngleDegrees(angle) { + + const s = Math.sign(angle); + const a = angle % 360 + + const b = (s === -1) ? + ((a <= -180) ? a + 360 : a) : + ((a > 180) ? a - 360 : a); + + return b; + } + + function clampAngleRadians(angle) { + let a = angle; + + while(a <= 0) { + a += Math.PI; + } + + return a + } + + function castAngle(angle) { + return String((Math.sign(angle) === -1) ? angle + 360 : angle); + } + + function isInteger(n) { + return Math.round(n) === n; + } + + /** + * Transform an Scratch Angle into Complex Plane Angle + * + * @param {number} angle Scratch Angle + * @returns {number} + */ + function transformAngle(angle) { + return -angle + 90 + } + + /** + * Transform an Complex Plane Angle into Scratch Angle + * + * @param {number} angle Complex Plane Angle + * @returns {number} + */ + function untransformAngle(angle) { + return -(angle - 90) + } + + function standarizeJSONObject(json) { + + const standardMap = { + real: ["real", "x"], + imaginary: ["imaginary", "y"], + absolute: ["absolute", "magnitude", "force"], + phase: ["phase", "argument", "angle", "rotation"] + } + const standarized = {}; + + for (const key of standardMap.real) { + if(json.hasOwnProperty(key)) { + standarized.real = json[key]; + } + } + for (const key of standardMap.imaginary) { + if(json.hasOwnProperty(key)) { + standarized.imaginary = json[key]; + } + } + for (const key of standardMap.absolute) { + if(json.hasOwnProperty(key)) { + standarized.absolute = json[key]; + } + } + for (const key of standardMap.phase) { + if(json.hasOwnProperty(key)) { + standarized.phase = json[key]; + } + } + + return standarized; + } + + + /** + * Forces one kind of complex number, either polar or rectangular + * + * @param {ComplexNumberType} complex + * @param {"polar"|"rectangular"} form + * @returns {ComplexNumberType} + */ + function forceForm(complex,form) { + switch (form) { + case "polar": + return new ComplexNumberType(complex.real, complex.imaginary, complex._modulus || complex.modulus, complex._phase || complex.phase); + + case "rectangular": + return new ComplexNumberType(complex.real, complex.imaginary); + } + } + + const Degrees = { + sin(x) { + return Math.sin(degreesToRadian(x)); + }, + cos(x) { + return Math.cos(degreesToRadian(x)); + }, + atan2(a,b) { + return radianToDegrees(Math.atan2(b,a)); + } + } + + // credits for many parts of this code to jwklong owo + + function span(text) { + let el = document.createElement('span') + el.innerHTML = text + el.style.display = 'hidden' + el.style.whiteSpace = 'nowrap' + el.style.width = '100%' + el.style.textAlign = 'center' + return el + } + + + /** + * A complex number + * + * @class ComplexNumberType + * @typedef {ComplexNumberType} + */ + class ComplexNumberType { + customId = "reisenComplexNumber"; + + + /** + * Creates an instance of ComplexNumberType. + * + * @constructor + * @param {number} [real=0] Real part + * @param {number} [imaginary=0] Imaginary part + * @param {number} [modulus] Modulus in case you're working with polars + * @param {number} [phase] Angle, in case you're working with polars, in degrees, transposed to the complex plane + */ + constructor(real = 0,imaginary = 0, modulus, phase) { + // console.log(modulus, angle) + if(!(typeof modulus == "undefined" || typeof phase == "undefined")) { + this._fromPolar = true; + this._modulus = modulus; + this._phase = clampAngleDegrees(phase); + + switch (this._phase) { + case 360: + case 0: + this.real = modulus; + this.imaginary = 0; + break; + + case 90: + this.real = 0 ; + this.imaginary = modulus; + break; + + case 180: + this.real = -modulus; + this.imaginary = 0; + break; + + case 270: + this.real = 0; + this.imaginary = -modulus; + break; + + default: + + this.real = (isNaN(real) | real == 0) ? modulus * Degrees.cos(phase) : real; + this.imaginary = (isNaN(imaginary) | imaginary == 0) ? modulus * Degrees.sin(phase) : imaginary; + break; + } + } else { + this._fromPolar = false; + this._modulus = null; + this._phase = null; + + this.real = isNaN(real) ? 0 : real; + this.imaginary = isNaN(imaginary) ? 0 : imaginary; + } + } + + static toComplex(u) { + if (u instanceof ComplexNumberType) { + if(u._fromPolar) { + return new ComplexNumberType(u.real, u.imaginary, u.modulus, clampAngleDegrees(u.phase)); + } else { + return new ComplexNumberType(u.real, u.imaginary); + } + } + if (vm.jwVector && u instanceof vm.jwVector.Type) { + return new ComplexNumberType(u.x, u.y) + }; + if (vm.jwArray && u instanceof vm.jwArray.Type) { + const s = u.array.map(c => Scratch.Cast.toNumber(c)); + if(s[3]) { + s[3] = transformAngle(s[3]); + } + return new ComplexNumberType(...u); + }; + if (u instanceof Array) { + const s = u.map(c => Scratch.Cast.toNumber(c)); + if(s[3]) { + s[3] = transformAngle(s[3]); + } + return new ComplexNumberType(...u); + } + if (typeof u == "number") { + return new ComplexNumberType(u); + } + if (String(u).split(',')) { + const s = String(u).split(',').map(c => Scratch.Cast.toNumber(c)); + if(s[3]) { + s[3] = transformAngle(s[3]); + } + return new ComplexNumberType(...s); + } + + try { + let parsed = JSON.parse(u) + if (parsed instanceof Array) { + if(s[3]) { + s[3] = transformAngle(s[3]); + } + return new ComplexNumberType(...u) + }; + + // if (parsed instanceof Object) { + // return new ComplexNumberType() + // }; + } catch {} + return new ComplexNumberType(0, 0); + } + + + /** + * Support for the Jwklong Array Handler! + * + * @returns {string} + */ + jwArrayHandler() { + return 'Complex'; + } + + /** + * Casts the complex number as a string, choose whether to use rectangular or polar form + * + * @param {boolean} [polarForm=false] Choose whether to convert it as polar form when casting to string + * @returns {string} + */ + toString(polarForm = false) { + if(polarForm) { + return `${this.modulus}∠${this.phase}°`; + } else { + return parseComplexToString(this.real,this.imaginary) + } + } + + toMonitorContent = () => span(parseComplexToString(this.real,this.imaginary)) + + toReporterContent() { + let root = document.createElement('div') + root.textContent = parseComplexToString(this.real,this.imaginary); + return root + } + + /** + * Returns the absolute value or modulus of a complex number + * @returns {number} + */ + get modulus() { + if(this._fromPolar) { + return this._modulus + } else { + return Math.hypot(this.real, this.imaginary) + } + } + + /** + * Returns the argument or phase of a complex number in radians + * @returns {number} + */ + get phase() { + if(this._fromPolar) { + return this._phase + } else { + return Degrees.atan2(this.real, this.imaginary) + } + } + + /** @returns {ComplexNumberType} */ + get conjugate() { + if(this._fromPolar) { + return new ComplexNumberType(this.real,-this.imaginary, this._modulus, -this._phase) + } else { + return new ComplexNumberType(this.real,-this.imaginary) + } + } + + + toJSON() { + if(this._fromPolar) { + return { + real: this.real, + imaginary: this.imaginary, + modulus: this._modulus, + phase: untransformAngle(this._phase) + } + } else { + return { + real: this.real, + imaginary: this.imaginary + } + } + } + + toArray() { + if(this._fromPolar) { + return [ this.real, this.imaginary, this._modulus, untransformAngle(this._phase)] + } else { + return [ this.real, this.imaginary ]; + } + } + + + /** + * Creates a complex number given it's polar form + * + * @static + * @param {number} modulus The absolute value, or modulus of the original number + * @param {number} phase The angle, in degrees + * @returns {ComplexNumberType} + */ + static fromPolar(modulus, phase) { + // const real = absolute * Degrees.cos(argument) + // const imaginary = absolute * Degrees.sin(argument) + return new ComplexNumberType(0, 0, modulus, clampAngleDegrees(phase)) + } + } + + const ComplexNumber = { + Type: ComplexNumberType, + Block: { + blockType: Scratch.BlockType.REPORTER, + blockShape: Scratch.BlockShape.BUMPED, + forceOutputType: "ComplexNumber", + disableMonitor: true + }, + + Argument: { + shape: Scratch.BlockShape.BUMPED, + check: [ "ComplexNumber" ] + }, + + /** + * Serializer for this type + * + * @param {ComplexNumberType} z Unserialized + * @returns {{}} + */ + Serializer(z) { + if(z._fromPolar) { + return [z.real, z.imaginary, z.modulus, untransformAngle(z.phase)]; + } else { + return [z.real, z.imaginary]; + } + }, + + + /** + * Deserializer for this type + * + * @param {[number,number]|[number,number,number,number]} z Serialized + * @returns {ComplexNumberType} + */ + Deserializer(z) { + if(s[3]) { + s[3] = transformAngle(s[3]); + } + return new ComplexNumber.Type(...z) + } + } + + class ComplexNumberExtension { + constructor() { + Scratch.vm.salagataComplexNumber = ComplexNumber, + // Scratch.vm.reisenComplexPolar = ComplexPolar, + Scratch.vm.runtime.registerSerializer( + "salagataComplexNumber", + ComplexNumber.Serializer, ComplexNumber.Deserializer + ) + + this.formatMessage = function (id) { + return Scratch.translate({ id: id, default: id }); + }; + + this.formatEveryBlock = function (blocks) { + // console.log("Before") + // console.log(blocks) + return blocks + // return blocks.map(block => { + // console.log("Loop") + // console.log(block) + // block.text = Scratch.translate({id: block.text, default: block.text}); + // console.log("Next") + // console.log(block.text) + // return block.text + // }) + } + } + + getInfo() { + return { + id: "salagataComplexNumber", + name: this.formatMessage("Complex Numbers"), + description: this.formatMessage("Complex Number Type for do complex analysis functions, perfect for rotation where vectors are slow"), + color1: "#c3ba5e", + menuIconURI: "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSIxMjEuNjIxNjIiIGhlaWdodD0iMTIxLjYyMTYyIiB2aWV3Qm94PSIwLDAsMTIxLjYyMTYyLDEyMS42MjE2MiI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTE3OS4xODkxOSwtMTE5LjE4OTE5KSI+PGcgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjAiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCI+PHBhdGggZD0iTTE3OS4xODkxOSwxODBjMCwtMzMuNTg0ODggMjcuMjI1OTMsLTYwLjgxMDgxIDYwLjgxMDgxLC02MC44MTA4MWMzMy41ODQ4OCwwIDYwLjgxMDgxLDI3LjIyNTkzIDYwLjgxMDgxLDYwLjgxMDgxYzAsMzMuNTg0ODggLTI3LjIyNTkzLDYwLjgxMDgxIC02MC44MTA4MSw2MC44MTA4MWMtMzMuNTg0ODgsMCAtNjAuODEwODEsLTI3LjIyNTkzIC02MC44MTA4MSwtNjAuODEwODF6IiBmaWxsPSIjYzNiYTVlIiBzdHJva2UtbGluZWNhcD0iYnV0dCIvPjxwYXRoIGQ9Ik0yNjIuMjMyNjksMTQ1LjA0NDA5YzAsNS4yMDA3NyAtNS4wMzI2Myw4LjYwNDQ2IC0xMi4zMTM3MSw4LjYwNDQ2Yy03LjI4MTA3LDAgLTEwLjc4NjUzLC0xLjEzNDE5IC0xMC43ODY1MywtNi4zMzQ5NWMwLC01LjIwMDc3IDQuNTc3NjgsLTkuODU5MjMgMTEuODU4NzUsLTkuODU5MjNjNy4yODEwOCwwIDExLjI0MTUsMi4zODg5NyAxMS4yNDE1LDcuNTg5NzR6IiBmaWxsPSIjZmZlYTAwIiBzdHJva2UtbGluZWNhcD0iYnV0dCIvPjxwYXRoIGQ9Ik0yNTEuNzQ3MDYsMTc3LjQ1Nzk4Yy0xLjY1ODEzLDMuNDYzNTggLTMuNTEyMzEsNi42ODI2OCAtNi45MDI3OCwxNC4zNjA4M2MtMy4wMTc0OCw2LjgzMzQ3IC0xMS45MjA0OCwyMi41MTA4MiAtOS45MDgzLDIzLjUxOTkyYzMuNTYwMzQsNS40MTE3MyA1LjgyMTY2LDQuODExMDMgMTMuNDkyMiw2LjU5Njk1Yy00LjE5Nzc1LDEuOTg4MDggLTIyLjAyNjk5LC0xLjUwMDUzIC0yNC41MTA2OSwtMS43MjgyNWMtMTEuNTg2NDcsLTEuMDYyMzIgLTQuNTg4MiwtMTcuMTg3OTQgMi45MTMxNSwtMzAuODg4MTJjMi4xNTEyLC0zLjkyODg4IDkuMzYyMTgsLTE1LjIyNjM4IDkuMTUzMzMsLTE4LjY4MTI3Yy0wLjQwNzg2LC02Ljc0NzExIC0xNy42OTM1OCwtNC44MzI4OCAtMTUuMzIxMTMsLTYuOTk0NzhjMi41MjM3NiwtMi4yOTk3OSAyMy41MTYyNiwtMC4xNzM0MyAyOC45MzMsMC43MzMxOWM0LjQzOTYyLDEuNDg3NjkgNi4yOTA0MSwxLjk2NjUyIDYuMjI5NTUsNC4xOTcyOGMtMC4wNzUxLDIuNzUyOTQgLTMuMTYzNzMsNi4yMjUxNyAtNC4wNzgzMyw4Ljg4NDI0eiIgZmlsbD0iI2ZmZWEwMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIi8+PC9nPjwvZz48L3N2Zz4=", + docsURI + // blockText: "#000000", + blocks: [ + { + opcode: "realToComplex", + text: this.formatMessage("complex number from [REAL]"), + arguments: { + REAL: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1 + } + }, + ...ComplexNumber.Block + }, + { + opcode: "imaginaryToComplex", + text: this.formatMessage("complex number from [IMAGINARY]i"), + arguments: { + IMAGINARY: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1 + } + }, + ...ComplexNumber.Block + }, + { + opcode: "newComplex", + text: this.formatMessage("complex number [REAL] + [IMAGINARY]i"), + arguments: { + REAL: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1 + }, + IMAGINARY: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1 + } + }, + ...ComplexNumber.Block + }, + { + opcode: "newComplexFromPolar", + text: this.formatMessage("complex number modulus: [R] phase: [PHASE]"), + arguments: { + R: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1 + }, + PHASE: { + type: Scratch.ArgumentType.ANGLE, + defaultValue: 45 + } + }, + ...ComplexNumber.Block + }, + { + opcode: "newComplexFromPolar2", + text: this.formatMessage("complex number modulus: 1 phase: [PHASE]"), + arguments: { + PHASE: { + type: Scratch.ArgumentType.ANGLE, + defaultValue: 45 + } + }, + ...ComplexNumber.Block + }, + { + opcode: "parseToComplex", + text: this.formatMessage("parse [A] to a complex number"), + arguments: { + A: { + type: Scratch.ArgumentType.STRING, + defaultValue: "1,1", + exemptFromNormalization: true + } + }, + blockType: Scratch.BlockType.REPORTER, + ...ComplexNumber.Block + }, + "---", + { + opcode: "getRealPart", + text: this.formatMessage("real part [A]"), + arguments: { + A: ComplexNumber.Argument + }, + blockType: Scratch.BlockType.REPORTER + }, + { + opcode: "getImaginaryPart", + text: this.formatMessage("imaginary part [A]"), + arguments: { + A: ComplexNumber.Argument + }, + blockType: Scratch.BlockType.REPORTER + }, + { + opcode: "getModulus", + text: this.formatMessage("absolute value [A]"), + arguments: { + A: ComplexNumber.Argument + }, + blockType: Scratch.BlockType.REPORTER + }, + { + opcode: "getPhase", + text: this.formatMessage("phase [A]"), + arguments: { + A: ComplexNumber.Argument + }, + blockType: Scratch.BlockType.REPORTER + }, + "---", + { + opcode: "add", + text: this.formatMessage("[A] + [B]"), + arguments: { + A: ComplexNumber.Argument, + B: ComplexNumber.Argument + }, + ...ComplexNumber.Block + }, + { + opcode: "subtract", + text: this.formatMessage("[A] - [B]"), + arguments: { + A: ComplexNumber.Argument, + B: ComplexNumber.Argument + }, + ...ComplexNumber.Block + }, + { + opcode: "multiply", + text: this.formatMessage("[A] x [B] using [FORM]"), + arguments: { + A: ComplexNumber.Argument, + B: ComplexNumber.Argument, + FORM: { + type: Scratch.ArgumentType.STRING, + menu: 'FORMS' + } + }, + ...ComplexNumber.Block + }, + { + opcode: "divide", + text: this.formatMessage("[A] / [B] using [FORM]"), + arguments: { + A: ComplexNumber.Argument, + B: ComplexNumber.Argument, + FORM: { + type: Scratch.ArgumentType.STRING, + menu: 'FORMS' + } + }, + ...ComplexNumber.Block + }, + "---", + { + opcode: "conjugate", + text: this.formatMessage("conjugate [A]"), + arguments: { + A: ComplexNumber.Argument + }, + ...ComplexNumber.Block + }, + { + opcode: "multiplyConjugate", + text: this.formatMessage("multiply [A] with its conjugate"), + arguments: { + A: ComplexNumber.Argument + }, + ...ComplexNumber.Block + }, + { + opcode: "reciprocal", + text: this.formatMessage("reciprocal [A]"), + arguments: { + A: ComplexNumber.Argument + }, + ...ComplexNumber.Block + }, + "---", + { + opcode: "complexToString", + text: this.formatMessage("[COMPLEX] to [FORM] as text"), + arguments: { + COMPLEX: ComplexNumber.Argument, + FORM: { + type: Scratch.ArgumentType.STRING, + menu: 'FORMS' + } + }, + blockType: Scratch.BlockType.REPORTER + }, + { + opcode: "forceForm", + text: this.formatMessage("use [FORM] for [COMPLEX]"), + arguments: { + COMPLEX: ComplexNumber.Argument, + FORM: { + type: Scratch.ArgumentType.STRING, + menu: 'FORMS' + } + }, + ...ComplexNumber.Block + }, + // "---", + // { + // opcode: "multiply2", + // text: this.formatMessage("multiply [A] with [B] using the polar form"), + // arguments: { + // A: ComplexNumber.Argument, + // B: ComplexNumber.Argument + // }, + // ...ComplexNumber.Block + // }, + // { + // opcode: "divide2", + // text: this.formatMessage("divide [A] with [B] using the polar form"), + // arguments: { + // A: ComplexNumber.Argument, + // B: ComplexNumber.Argument + // }, + // ...ComplexNumber.Block + // }, + "---", + { + opcode: "power", + text: this.formatMessage("[A] ^ [B] using [FORM]"), + arguments: { + A: ComplexNumber.Argument, + B: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 2 + }, + FORM: { + type: Scratch.ArgumentType.STRING, + menu: 'FORMS' + } + }, + ...ComplexNumber.Block + }, + { + opcode: "squareRoot", + text: this.formatMessage("square root of [A]"), + arguments: { + A: ComplexNumber.Argument, + }, + ...ComplexNumber.Block + }, + // { + // opcode: "power2", + // text: this.formatMessage("[A] ^ [B] using the polar form"), + // arguments: { + // A: ComplexNumber.Argument, + // B: { + // type: Scratch.ArgumentType.NUMBER, + // defaultValue: 2 + // }, + // }, + // ...ComplexNumber.Block + // }, + { + opcode: "nRoot", + text: this.formatMessage("[B]th root of [A] using the polar form"), + arguments: { + A: ComplexNumber.Argument, + B: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 2 + }, + }, + ...ComplexNumber.Block + }, + { + opcode: "complexPower", + text: this.formatMessage("[A] ^ [B]"), + arguments: { + A: ComplexNumber.Argument, + B: ComplexNumber.Argument, + }, + ...ComplexNumber.Block + }, + "---", + { + opcode: "exponential", + text: this.formatMessage("e^[B]i pi"), + arguments: { + B: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1 + }, + }, + ...ComplexNumber.Block + }, + { + opcode: "exponential2", + text: this.formatMessage("e^[B]i"), + arguments: { + B: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1 + }, + }, + ...ComplexNumber.Block + }, + { + opcode: "exponential3", + text: this.formatMessage("e^[B]"), + arguments: { + B: ComplexNumber.Argument, + }, + ...ComplexNumber.Block + }, + { + opcode: "naturalLogarithm", + text: this.formatMessage("ln [A]"), + arguments: { + A: ComplexNumber.Argument, + }, + ...ComplexNumber.Block + }, + // { + // opcode: "multiply", + // text: this.formatMessage("multiply [A] with [B] in polar form"), + // arguments: { + // A: ComplexNumber.Argument, + // B: ComplexNumber.Argument + // }, + // ...ComplexNumber.Block + // }, + { + opcode: "quadraticEquation", + text: this.formatMessage("solutions of equation [A]x^2 + [B]x + [C] = 0"), + arguments: { + A: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1 + }, + B: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 0 + }, + C: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1 + }, + }, + + blockType: Scratch.BlockType.REPORTER, + blockShape: Scratch.BlockShape.SQUARE, + disableMonitor: true + }, + { + opcode: "quadraticEquation2", + text: this.formatMessage("[SOLUTION] solution of equation [A]x^2 + [B]x + [C] = 0"), + arguments: { + A: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1 + }, + B: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 0 + }, + C: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1 + }, + SOLUTION: { + type: Scratch.ArgumentType.STRING, + menu: 'SOLUTIONS' + } + }, + ...ComplexNumber.Block + }, + { + opcode: "roots2", + text: this.formatMessage("roots of equation [C]x^[A] - [B] = 0"), + arguments: { + A: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1 + }, + B: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 0 + }, + C: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1 + }, + }, + + blockType: Scratch.BlockType.REPORTER, + blockShape: Scratch.BlockShape.SQUARE, + disableMonitor: true + }, + { + opcode: "roots4", + text: this.formatMessage("[D]th root of equation [C]x^[A] - [B] = 0"), + arguments: { + A: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1 + }, + B: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 0 + }, + C: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1 + }, + D: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1 + }, + }, + ...ComplexNumber.Block + }, + "---", + { + opcode: 'getPos', + text: this.formatMessage('position in [FORM]'), + blockText: null, + extensions: ["colours_motion"], + filter: [Scratch.TargetType.SPRITE], + arguments: { + FORM: { + type: Scratch.ArgumentType.STRING, + menu: 'FORMS' + }, + }, + ...ComplexNumber.Block + }, + { + opcode: 'getDirection', + text: this.formatMessage('direction in [FORM]'), + blockText: null, + extensions: ["colours_motion"], + filter: [Scratch.TargetType.SPRITE], + arguments: { + FORM: { + type: Scratch.ArgumentType.STRING, + menu: 'FORMS' + }, + }, + ...ComplexNumber.Block + }, + { + opcode: 'setPos', + text: this.formatMessage('go to [COMPLEX] using [FORM]'), + arguments: { + COMPLEX: ComplexNumber.Argument, + FORM: { + type: Scratch.ArgumentType.STRING, + menu: 'FORMS' + }, + }, + blockText: null, + extensions: ["colours_motion"], + filter: [Scratch.TargetType.SPRITE] + }, + { + opcode: 'pointTowards', + text: this.formatMessage('point towards [COMPLEX] using [FORM]'), + arguments: { + COMPLEX: ComplexNumber.Argument, + FORM: { + type: Scratch.ArgumentType.STRING, + menu: 'FORMS' + }, + }, + blockText: null, + extensions: ["colours_motion"], + filter: [Scratch.TargetType.SPRITE] + }, + "---", + { + opcode: 'getStretch', + text: this.formatMessage('stretch in [FORM]'), + blockText: null, + extensions: ["colours_looks"], + filter: [Scratch.TargetType.SPRITE], + arguments: { + FORM: { + type: Scratch.ArgumentType.STRING, + menu: 'FORMS' + }, + }, + ...ComplexNumber.Block + }, + { + opcode: 'setStretch', + text: this.formatMessage('set stretch to [COMPLEX] using [FORM]'), + arguments: { + COMPLEX: ComplexNumber.Argument, + FORM: { + type: Scratch.ArgumentType.STRING, + menu: 'FORMS' + }, + }, + blockText: null, + extensions: ["colours_looks"], + filter: [Scratch.TargetType.SPRITE] + }, + "---", + { + opcode: 'getMouse', + text: this.formatMessage('mouse position in [FORM]'), + blockText: null, + extensions: ["colours_sensing"], + arguments: { + FORM: { + type: Scratch.ArgumentType.STRING, + menu: 'FORMS' + }, + }, + ...ComplexNumber.Block + }, + "---", + { + opcode: 'transformAngle', + text: this.formatMessage("transform [ANGLE] into Complex Plane Angle"), + blockText: null, + extensions: ["colours_operators"], + arguments: { + ANGLE: { + type: Scratch.ArgumentType.ANGLE, + defaultValue: 60 + } + }, + blockType: Scratch.BlockType.REPORTER, + blockShape: Scratch.BlockShape.ROUND, + disableMonitor: true + }, + { + opcode: 'untransformAngle', + text: this.formatMessage("transform [ANGLE] into Scratch Angle"), + blockText: null, + extensions: ["colours_operators"], + arguments: { + ANGLE: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 30 + } + }, + blockType: Scratch.BlockType.REPORTER, + blockShape: Scratch.BlockShape.ROUND, + disableMonitor: true + }, + + ...(Scratch.vm.runtime.ext_jwVector ? ["---"] : []), + { + opcode: "toVector", + text: this.formatMessage("convert [COMPLEX] to vector"), + arguments: { + COMPLEX: ComplexNumber.Argument, + }, + blockType: Scratch.BlockType.REPORTER, + blockShape: Scratch.BlockShape.LEAF, + disableMonitor: true, + hideFromPalette: !Scratch.vm.runtime.ext_jwVector, + ...(Scratch.vm.jwVector ? Scratch.vm.jwVector.Block : {}) + }, + { + opcode: "fromVector", + text: this.formatMessage("convert [VECTOR] to complex number"), + arguments: { + VECTOR: { + ...(Scratch.vm.jwVector ? Scratch.vm.jwVector.Argument : {}) + }, + }, + blockType: Scratch.BlockType.REPORTER, + disableMonitor: true, + hideFromPalette: !Scratch.vm.runtime.ext_jwVector, + ...ComplexNumber.Block + }, + + + // ...(Scratch.vm.runtime.ext_jwArray ? ["---"] : []), + // { + // opcode: "roots3", + // text: this.formatMessage("roots of equation [C]x^[A] - [B] = 0"), + // arguments: { + // A: { + // type: Scratch.ArgumentType.NUMBER, + // defaultValue: 1 + // }, + // B: { + // type: Scratch.ArgumentType.NUMBER, + // defaultValue: 0 + // }, + // C: { + // type: Scratch.ArgumentType.NUMBER, + // defaultValue: 1 + // }, + // }, + + // blockType: Scratch.BlockType.REPORTER, + // blockShape: Scratch.BlockShape.SQUARE, + // disableMonitor: true, + // hideFromPalette: !Scratch.vm.runtime.ext_jwArray, + // ...(Scratch.vm.jwArray ? Scratch.vm.jwArray.Block : {}) + // }, + + ], + menus: { + SOLUTIONS: { + acceptReporters: false, + items: [ + { + text: this.formatMessage("first"), + value: "first" + }, + { + text: this.formatMessage("second"), + value: "second" + }, + ] + }, + FORMS: { + acceptReporters: false, + items: [ + { + text: this.formatMessage("rectangular form"), + value: "rectangular" + }, + { + text: this.formatMessage("polar form"), + value: "polar" + }, + ] + } + } + } + } + + /** + * Creates a new complex number given the real and imaginary part + * + * @param { number } args.REAL + * @param { number } args.IMAGINARY + * @returns {ComplexNumberType} + */ + newComplex(args) { + const real = Scratch.Cast.toNumber(args.REAL) + const imaginary = Scratch.Cast.toNumber(args.IMAGINARY) + + return new ComplexNumberType(real,imaginary); + } + + + /** + * Given the polar form creates a complex + * + * @param { number } args.R + * @param { number } args.PHASE + * @returns {ComplexNumberType} + */ + newComplexFromPolar(args) { + const modulus = Scratch.Cast.toNumber(args.R) + const phase = transformAngle(Scratch.Cast.toNumber(args.PHASE)); + + return ComplexNumberType.fromPolar(modulus,phase); + // z = r(cos a + i sin a) + // const real = modulus * Math.cos(phase); + // const imaginary = modulus * Math.sin(phase); + + } + + /** + * Given the phase creates a complex assuming modulus is 1 + * + * @param { number } args.PHASE + * @returns {ComplexNumberType} + */ + newComplexFromPolar2(args) { + const phase = transformAngle(Scratch.Cast.toNumber(args.PHASE)); + + return ComplexNumberType.fromPolar(1,phase); + // z = r(cos a + i sin a) + // const real = modulus * Math.cos(phase); + // const imaginary = modulus * Math.sin(phase); + } + + + /** + * Converts a single real number into a complex type + * + * @param { number } args.REAL + * @returns {ComplexNumberType} + */ + realToComplex(args) { + return new ComplexNumberType(args.REAL,0) + } + + /** + * Converts a sole imaginary number into a complex type + * + * @param { number } args.IMAGINARY + * @returns {ComplexNumberType} + */ + imaginaryToComplex(args) { + return new ComplexNumberType(0,args.IMAGINARY) + } + + getRealPart(args) { + return ComplexNumberType.toComplex(args.A).real + } + + getImaginaryPart(args) { + return ComplexNumberType.toComplex(args.A).imaginary + } + + getModulus(args) { + return ComplexNumberType.toComplex(args.A).modulus + } + + getPhase(args) { + return untransformAngle(ComplexNumberType.toComplex(args.A).phase) + } + + parseToComplex(args) { + return ComplexNumberType.toComplex(args.A); + } + + conjugate(args) { + return ComplexNumberType.toComplex(args.A).conjugate + } + + add(args) { + const A = ComplexNumberType.toComplex(args.A); + const B = ComplexNumberType.toComplex(args.B); + + return new ComplexNumberType(A.real + B.real, A.imaginary + B.imaginary); + } + + subtract(args) { + const A = ComplexNumberType.toComplex(args.A); + const B = ComplexNumberType.toComplex(args.B); + + return new ComplexNumberType(A.real - B.real, A.imaginary - B.imaginary); + } + + multiply(args) { + const A = ComplexNumberType.toComplex(args.A); + const B = ComplexNumberType.toComplex(args.B); + const FORM = Scratch.Cast.toString(args.FORM); + + switch (FORM) { + case "polar": + return new ComplexNumberType( + A.real * B.real - A.imaginary * B.imaginary, + A.real * B.imaginary + A.imaginary * B.real + , A.modulus * B.modulus + , (A.phase + B.phase) + ); + + case "rectangular": + return new ComplexNumberType( + A.real * B.real - A.imaginary * B.imaginary, + A.real * B.imaginary + A.imaginary * B.real + ); + } + + + + } + + divide(args) { + const A = ComplexNumberType.toComplex(args.A); + const B = ComplexNumberType.toComplex(args.B); + const FORM = Scratch.Cast.toString(args.FORM); + const u = B.real ** 2 + B.imaginary ** 2; + + switch (FORM) { + case "polar": + return new ComplexNumberType( + (A.real * B.real + A.imaginary * B.imaginary) / u, + (A.imaginary * B.real - A.real * B.imaginary) / u + , A.modulus / B.modulus + , (A.phase - B.phase) + ); + case "rectangular": + + return new ComplexNumberType( + (A.real * B.real + A.imaginary * B.imaginary) / u, + (A.imaginary * B.real - A.real * B.imaginary) / u + ); + + } + } + + multiplyConjugate(args) { + const A = ComplexNumberType.toComplex(args.A); + + if(A._fromPolar) { + return new ComplexNumberType(A.real ** 2 + A.imaginary ** 2,0, + A.modulus ** 2, 0); + } else { + return new ComplexNumberType(A.real ** 2 + A.imaginary ** 2,0); + } + } + + reciprocal(args) { + const A = ComplexNumberType.toComplex(args.A); + const u = A.real ** 2 + A.imaginary ** 2; + + if(A._fromPolar) { + return new ComplexNumberType(A.real / u, -A.imaginary / u, + 1 / A.modulus, -A.phase); + } else { + return new ComplexNumberType(A.real / u, -A.imaginary / u); + } + + } + + /** + * Forces one kind of complex number, either polar or rectangular + * + * @param {ComplexNumberType} args.COMPLEX + * @param {"polar"|"rectangular"} args.FORM + * @returns {ComplexNumberType} + */ + forceForm(args) { + const COMPLEX = ComplexNumberType.toComplex(args.COMPLEX); + const FORM = Scratch.Cast.toString(args.FORM); + + return forceForm(COMPLEX,FORM); + } + + /** + * Converts a polar number or a complex number into a text + * + * @param {ComplexNumberType} args.COMPLEX + * @param {"polar"|"rectangular"} args.FORM + * @returns {ComplexNumberType} + */ + complexToString(args) { + const COMPLEX = ComplexNumberType.toComplex(args.COMPLEX); + const FORM = Scratch.Cast.toString(args.FORM); + + + return COMPLEX.toString(FORM == "polar"); + } + + power(args) { + const A = ComplexNumberType.toComplex(args.A); + let power = Scratch.Cast.toNumber(args.B); + const FORM = Scratch.Cast.toString(args.FORM); + + switch (FORM) { + case "polar": + // quickier and accepts decimals + if(power == 0) { + return new ComplexNumberType(1,0) + } + if(power == 1) { + return A + } + + const r = A.modulus ** power; + const phi = A.phase * power; + + return new ComplexNumberType( + r * Degrees.cos(phi), + r * Degrees.sin(phi), + r, phi + ); + case "rectangular": + power = Math.round(power); // This uses the definition of product for integer powers, + // still can't do an rectangular form-only for do decimal powers of complex numbers + + const firstReal = A.real, firstImaginary = A.imaginary; + let pair = [firstReal, firstImaginary]; + + if(power == 0) { + return new ComplexNumberType(1,0) + } + if(power == 1) { + return A + } + if(power == -1) { + const u = A.real ** 2 + A.imaginary ** 2; + return new ComplexNumberType(A.real / u, -A.imaginary / u); + } + + const absPower = Math.abs(power); + + for (let _ = 1; _ < absPower; _++) { + pair = [ + pair[0] * firstReal - pair[1] * firstImaginary, + pair[0] * firstImaginary + pair[1] * firstReal + ]; + } + + if(power < -1) { + const u = pair[0] ** 2 + pair[1] ** 2; + return new ComplexNumberType(pair[0] / u, -pair[1] / u); + + } + + return new ComplexNumberType( + pair[0], pair[1] + ); + + } + } + + + + squareRoot(args) { + const A = ComplexNumberType.toComplex(args.A); + const r = Math.hypot(A.real, A.imaginary); + + return new ComplexNumberType( + Math.sqrt(1/2 * (r + A.real)), + (A.imaginary >= 0 ? 1 : -1) * Math.sqrt(1/2 * (r - A.real)), + ); + } + + nRoot(args) { + const A = ComplexNumberType.toComplex(args.A); + const subRadical = Scratch.Cast.toNumber(args.B); + + if(subRadical == 0) { + return Infinity + } + if(subRadical == 1) { + return A + } + + const r = subRadical == 2 ? Math.sqrt(A.modulus) : (A.modulus ** (1/subRadical)); + const phi = A.phase / subRadical; + + return new ComplexNumberType( + r * Degrees.cos(phi), + r * Degrees.sin(phi), + r, phi + ); + } + + // power3(args) { + // const A = ComplexNumberType.toComplex(args.A); + // const power = Math.round(Scratch.Cast.toNumber(args.B)); + + // if(power == 0) { + // return new ComplexNumberType(1,0) + // } + // if(power == 1) { + // return A + // } + + // const r = A.absolute ** n; + // const phi = A.argument * n; + + // return new ComplexNumberType( + // undefined, undefined, + // r * Degrees.cos(phi), + // r * Degrees.sin(phi) + // ); + // } + exponential(args) { + const radians = Scratch.Cast.toNumber(args.B); + if(radians == 0) { + return new ComplexNumberType(1,0,1,180); + } + if(radians == 1) { + return new ComplexNumberType(-1,0,1,180); + } + + const real = Math.cos(radians * Math.PI); + const imaginary = Math.sin(radians * Math.PI); + + return new ComplexNumberType(real, imaginary, 1, radianToDegrees(radians * Math.PI)) + } + exponential2(args) { + const radians = Scratch.Cast.toNumber(args.B); + if(radians == 0) { + return new ComplexNumberType(1,0,1,180); + } + if(radians == Math.PI) { + return new ComplexNumberType(-1,0,1,180); + } + + const real = Math.cos(radians); + const imaginary = Math.sin(radians); + + return new ComplexNumberType(real, imaginary, 1, radianToDegrees(radians)) + } + + + /** + * Calculates exp(z) or e^z of a complex number + * + * @param {ComplexNumberType} complex A complex number + * @returns {ComplexNumberType} Exponentiated + */ + _exp(complex) { + const radians = complex.imaginary; + const ea = complex.real == 0 ? 1 : Math.exp(complex.real); + + if(radians == 0) { + return new ComplexNumberType(ea,0,ea,0); + } + if(radians == Math.PI) { + return new ComplexNumberType(-ea,0,ea,180); + } + + const real = ea * Math.cos(radians); + const imaginary = ea * Math.sin(radians); + + return new ComplexNumberType(real, imaginary, ea, radianToDegrees(radians)) + } + + exponential3(args) { + // e^(a+bi) = e^a * e^bi + const complex = ComplexNumberType.toComplex(args.B); + + return this._exp(complex) + } + + + /** + * Direct Natural logarithm of a complex number + * + * @param {ComplexNumberType} complex Complex + * @returns {ComplexNumberType} The Natural Logarithm + */ + _ln(complex) { + const A = complex; + if(A.real == 0 && A.imaginary == 0) { + return NaN; + } + if(A.imaginary == 0) { + if(A.real > 0) { + return new ComplexNumberType(Math.log(A.real)); + } else { + return new ComplexNumberType(Math.log(Math.abs(A.real)),Math.PI); + } + } + if(A.real == 0) { + if(A.imaginary > 0) { + return new ComplexNumberType(Math.log(A.imaginary), Math.PI / 2); + } else { + return new ComplexNumberType(Math.log(Math.abs(A.imaginary)), -Math.PI / 2); + } + } + + const r = Math.log(A.modulus); + const arg = degreesToRadian(A.phase); + + return new ComplexNumberType(r,arg); + } + + naturalLogarithm(args) { + const A = ComplexNumberType.toComplex(args.A); + + return this._ln(A); + } + + complexPower(args) { + // (a+bi)^(c+di) = e^[(c+di)*ln(a+bi)] + const A = ComplexNumberType.toComplex(args.A); + const B = ComplexNumberType.toComplex(args.B); + + const lnA = this._ln(A); + + let blnA + if(B._fromPolar) { + blnA = new ComplexNumberType( + lnA.real * B.real - lnA.imaginary * B.imaginary, + lnA.real * B.imaginary + lnA.imaginary * B.real + , lnA.modulus * B.modulus + , (lnA.phase + B.phase) + ); + } else { + blnA = new ComplexNumberType( + lnA.real * B.real - lnA.imaginary * B.imaginary, + lnA.real * B.imaginary + lnA.imaginary * B.real + ); + } + + return this._exp(blnA); + + + // if(A.real == 0 && A.imaginary == 0) { + // return new ComplexNumberType(1); + // } + // if(A.imaginary == 0) { + // // a^(c+di) = e^[(c+di)*ln(a)] + + // const lnA = this._ln(new ComplexNumberType(A.real)); + + // // if(A.real > 0) { + // // return new ComplexNumberType(Math.log(A.real)); + // // } else { + // // return new ComplexNumberType(Math.log(Math.abs(A.real)),Math.PI); + // // } + // } + // if(A.real == 0) { + // if(A.imaginary > 0) { + // return new ComplexNumberType(Math.log(A.imaginary), Math.PI / 2); + // } else { + // return new ComplexNumberType(Math.log(Math.abs(A.imaginary)), -Math.PI / 2); + // } + // } + + // const complex = B; + // const radians = complex.imaginary; + // const ea = complex.real == 0 ? 1 : Math.exp(complex.real); + + // if(radians == 0) { + // return new ComplexNumberType(ea,0,ea,0); + // } + // if(radians == Math.PI) { + // return new ComplexNumberType(-ea,0,ea,180); + // } + + // const real = ea * Math.cos(radians); + // const imaginary = ea * Math.sin(radians); + + // return new ComplexNumberType(real, imaginary, ea, radianToDegrees(radians)) + } + + + /** + * Solve an equation of the form ax^2 + bx + c = 0 + * + * @param {number} a Quadratic component + * @param {number} b Linear component + * @param {number} c Inpedentent component + * @returns {[ComplexNumberType,ComplexNumberType]} Solutions + */ + _quadratic(a,b,c) { + if(a == 0) { + throw new Error("Quadratic component can't be 0"); + } + + const det = b ** 2 - 4 * a * c; + let solutions = []; + + if(det > 0) { + const positive = ( -b + Math.sqrt(det)) / (2 * a); + const negative = ( -b - Math.sqrt(det)) / (2 * a); + + solutions = [ new ComplexNumberType(positive,0), new ComplexNumberType(negative,0) ]; + } + + if(det == 0) { + const unique = ( -b ) / (2 * a); + + solutions = [ new ComplexNumberType(unique,0), new ComplexNumberType(unique,0) ]; + } + + if(det < 0) { + const positive = new ComplexNumberType(-b / (2 * a), Math.sqrt(Math.abs(det)) / (2 * a)); + const negative = new ComplexNumberType(-b / (2 * a), -Math.sqrt(Math.abs(det)) / (2 * a)); + + solutions = [ positive, negative ]; + } + + return solutions; + } + + quadraticEquation(args) { + const a = Math.round(Scratch.Cast.toNumber(args.A)); + const b = Math.round(Scratch.Cast.toNumber(args.B)); + const c = Math.round(Scratch.Cast.toNumber(args.C)); + + return this._quadratic(a,b,c); + } + quadraticEquation2(args) { + const a = Math.round(Scratch.Cast.toNumber(args.A)); + const b = Math.round(Scratch.Cast.toNumber(args.B)); + const c = Math.round(Scratch.Cast.toNumber(args.C)); + + const solutions = this._quadratic(a,b,c); + + switch (args.SOLUTION) { + case "first": + return solutions[0]; + + case "second": + return solutions[1]; + + default: + break; + } + } + + // roots(args) { + // const a = Math.round(Math.abs(Scratch.Cast.toNumber(args.A))); + // const b = Scratch.Cast.toNumber(args.B); + + // if(a == 0) { + // return NaN; + // } + // if(a == 1) { + // return [ ComplexNumberType.toComplex(a) ]; + // } + + // let roots = []; + + // const r = a == 2 ? Math.sqrt(b) : (b ** (1/a)); + // for (let k = 0; k < a; k++) { + + // const phi = (0 + k * 360) / a; + + // roots.push(new ComplexNumberType( + // 0, // r * Degrees.cos(phi), + // 0, // r * Degrees.sin(phi), + // r, phi + // )); + // } + + // return roots; + // } + + + /** + * Solve the equation of the form cz^a - b = 0 + * + * @param {number} a Positive integer power + * @param {number} b Independent component + * @param {number} c Factor + * + * @returns {NaN | Array} Solutions + */ + _poly(a,b,c) { + if(a == 0) { + return NaN; + } + if(a == 1) { + return [ ComplexNumberType.toComplex(a) ]; + } + + let roots = []; + + const r = a == 2 ? Math.sqrt(b)/Math.sqrt(c) : ((b ** (1/a))/(c ** (1/a))); + for (let k = 0; k < a; k++) { + + const phi = (0 + k * 360) / a; + + roots.push(new ComplexNumberType( + 0, // r * Degrees.cos(phi), + 0, // r * Degrees.sin(phi), + r, phi + )); + } + + return roots; + } + + roots2(args) { + const a = Math.round(Math.abs(Scratch.Cast.toNumber(args.A))); + const b = Scratch.Cast.toNumber(args.B); + const c = Scratch.Cast.toNumber(args.C); + + return this._poly(a,b,c); + } + + // roots3(args) { + // const a = Math.round(Math.abs(Scratch.Cast.toNumber(args.A))); + // const b = Scratch.Cast.toNumber(args.B); + // const d = Math.round(Math.abs(Scratch.Cast.toNumber(args.D))); + + // // const c = Scratch.Cast.toNumber(args.C); + + // if(a == 0) { + // return NaN; + // } + // if(a == 1) { + // return [ ComplexNumberType.toComplex(a) ]; + // } + + // let roots = []; + + // const r = a == 2 ? Math.sqrt(b) : (b ** (1/a)); + + // const phi = (0 + d * 360) / a; + + // return new ComplexNumberType( + // 0, // r * Degrees.cos(phi), + // 0, // r * Degrees.sin(phi), + // r, phi + // ); + // } + + roots4(args) { + // d-th solution of equation cz^a - b = 0 + const a = Math.round(Math.abs(Scratch.Cast.toNumber(args.A))); + const b = Scratch.Cast.toNumber(args.B); + + const c = Scratch.Cast.toNumber(args.C); + const d = Math.round(Math.abs(Scratch.Cast.toNumber(args.D))); + + if(a == 0) { + return NaN; + } + if(a == 1) { + return [ ComplexNumberType.toComplex(a) ]; + } + + let roots = []; + + const r = a == 2 ? Math.sqrt(b)/Math.sqrt(c) : ((b ** (1/a))/(c ** (1/a))); + + const phi = (0 + d * 360) / a; + + return new ComplexNumberType( + 0, // r * Degrees.cos(phi), + 0, // r * Degrees.sin(phi), + r, phi + ); + } + + // Integrations with Scratch for doing things easier <3 + + getPos(args, util) { + const FORM = Scratch.Cast.toString(args.FORM); + return forceForm(new ComplexNumberType(util.target.x,util.target.y), FORM); + } + + getDirection(args, util) { + const FORM = Scratch.Cast.toString(args.FORM); + return forceForm(ComplexNumberType.fromPolar(1, transformAngle(util.target.direction)), FORM); + } + + setPos(args, util) { + const FORM = Scratch.Cast.toString(args.FORM); + const POSITION = forceForm(ComplexNumberType.toComplex(args.COMPLEX), FORM); + + util.target.setXY(POSITION.real, POSITION.imaginary) + } + + pointTowards(args, util) { + const FORM = Scratch.Cast.toString(args.FORM); + const POSITION = forceForm(ComplexNumberType.toComplex(args.COMPLEX), FORM); + util.target.setDirection(untransformAngle(POSITION.phase)); + // return forceForm(new ComplexNumberType(undefined, undefined, 1, util.target.direction), FORM); + } + + getStretch(args, util) { + const FORM = Scratch.Cast.toString(args.FORM); + return forceForm(new ComplexNumberType(...util.target.stretch), FORM) + } + + setStretch(args, util) { + const FORM = Scratch.Cast.toString(args.FORM); + const STRETCH = forceForm(ComplexNumberType.toComplex(args.COMPLEX), FORM) + + util.target.setStretch(STRETCH.real, STRETCH.imaginary) + } + + getMouse(args, util) { + const FORM = Scratch.Cast.toString(args.FORM); + + return forceForm(new ComplexNumberType(vm.runtime.ioDevices.mouse.getScratchX(), vm.runtime.ioDevices.mouse.getScratchY()), FORM) + } + + toVector(args) { + const COMPLEX = ComplexNumberType.toComplex(args.COMPLEX); + + return new Scratch.vm.jwVector.Type(COMPLEX.real, COMPLEX.imaginary) + } + + fromVector(args) { + const VECTOR = Scratch.vm.jwVector.Type.toVector(args.VECTOR); + + return new ComplexNumberType(VECTOR.x, VECTOR.y) + + } + + transformAngle(args) { + const angle = Scratch.Cast.toNumber(args.ANGLE); + + return transformAngle(angle); + } + + untransformAngle(args) { + const angle = Scratch.Cast.toNumber(args.ANGLE); + + return untransformAngle(angle); + } + + + } + Scratch.extensions.register( new ComplexNumberExtension() ) +})(Scratch) \ No newline at end of file diff --git a/static/extensions/salagata/reisenComplex.js b/static/extensions/salagata/reisenComplex.js deleted file mode 100644 index 07de04c6a..000000000 --- a/static/extensions/salagata/reisenComplex.js +++ /dev/null @@ -1,1387 +0,0 @@ - -(function (Scratch) { - 'use strict'; - if (!Scratch.extensions.unsandboxed) { - throw new Error('somehow \'Complex Numbers\' must run unsandboxed'); - } - - // 1 - // -1 - // i - // -i - // 1 + i - // 1 - i - // -1 + i - // -1 - i - - // function parseStringToComplex(str) { - - // } - Scratch.translate.setup({ - es: { - "Complex Numbers": "Números Complejos", - "Complex Number Type for do complex analysis functions, better implementation than the one made by jwklong in Mathemathics extension lmao": - "El tipo de dato de Números complejos para realizar funciones de analisis complejo, mejor implementación que la que jwklong hizo en la extensión Mathemathics", - "complex number from [REAL]": "número complejo desde [REAL]", - "complex number from [IMAGINARY]i": "número complejo desde [IMAGINARY]i", - "complex number [REAL] + [IMAGINARY]i": "número complejo [REAL] + [IMAGINARY]i", - "complex number modulus: [R] phase: [PHASE]": "número complejo de módulo: [R] fase: [PHASE]", - "complex number modulus: 1 phase: [PHASE]": "número complejo de módulo: 1 fase [PHASE]", - "real part [A]": "parte real [A]", - "imaginary part [A]": "parte imaginaria [A]", - "absolute value [A]": "valor absoluto [A]", - "phase [A]": "fase [A]", - "conjugate [A]": "conjugado [A]", - "multiply [A] with its conjugate":"multiplicar [A] con su conjugado", - "reciprocal [A]": "recíproca [A]", - // "complex number in polar form modulus: [R] phase: [PHASE]": "número complejo en forma polar modulo: [R] fase [PHASE]", - "[POLAR] to rectangular form": "[POLAR] a forma rectangular", - "[COMPLEX] to polar form": "[COMPLEX] a forma polar", - "multiply [A] with [B] using the polar form": "multiplicar [A] con [B] usando la forma polar", - "divide [A] with [B] using the polar form": "dividir [A] con [B] usando la forma polar", - "[A] ^ [B] using the polar form": "[A] ^ [B] usando la forma polar", - "squareroot of [A]": "raíz cuadrada de [A]", - "[B]th root of [A] using the polar form": "[B]ésima raíz de [A] usando la forma polar", - "solutions of equation [A]x^2 + [B]x + [C] = 0": "soluciones de la ecuación [A]x^2 + [B]x + [C] = 0", - "[SOLUTION] solution of equation [A]x^2 + [B]x + [C] = 0": "[SOLUTION] solución de la ecuación [A]x^2 + [B]x + [C] = 0", - "positive": "positiva", - "negative": "negativa", - "roots of equation x^[A] - [B] = 0": "raices de la ecuación x^[A] - [B] = 0", - "roots of equation [C]x^[A] - [B] = 0": "raices de la ecuación [C]x^[A] - [B] = 0", - "[D]th root of equation x^[A] - [B] = 0": "[D]ava raíz de la ecuación x^[A] - [B] = 0", - "[D]th root of equation [C]x^[A] - [B] = 0": "[D]ava raíz de la ecuación [C]x^[A] - [B] = 0" - }, - }); - - function radianToDegrees(radian) { - return radian * (180 / Math.PI); - } - - function degreesToRadian(degree) { - return degree * (Math.PI / 180); - } - - function parseComplexToString(real,imaginary) { - let str = ""; - if(!real && !imaginary) { - return "0"; - } - if(real) { - // 1 - // -1 - str += String(real); - if(imaginary) { - if(imaginary > 0) { - str += "+"; - } - str += String(imaginary) + "i"; - } - } else { - // i - // -i - str += String(imaginary) + "i" - } - return str - } - - function roundToDigits(n,d) { - const dd = 10 ** d - return Math.floor(n * dd) / dd - } - - /** - * @param {number} x - * @returns {string} - */ - function formatNumber(x) { - if (x >= 1e6) { - return x.toExponential(4) - } else { - x = Math.floor(x * 1000) / 1000 - return x.toFixed(Math.min(3, (String(x).split('.')[1] || '').length)) - } - } - - function clampAngleDegrees(angle) { - - const s = Math.sign(angle); - const a = angle % 360 - - const b = (s === -1) ? - ((a <= -180) ? a + 360 : a) : - ((a > 180) ? a - 360 : a); - - return b; - } - - function clampAngleRadians(angle) { - let a = angle; - - while(a <= 0) { - a += Math.PI; - } - - return a - } - - function castAngle(angle) { - return String((Math.sign(angle) === -1) ? angle + 360 : angle); - } - - function transposeAngle(angle) { - return -angle + 90 - } - - function untransposeAngle(angle) { - return -(angle - 90) - } - - const Degrees = { - sin(x) { - return Math.sin(degreesToRadian(transposeAngle(x))); - }, - cos(x) { - return Math.cos(degreesToRadian(transposeAngle(x))); - }, - atan2(a,b) { - return radianToDegrees(Math.atan2(a,b)); - } - } - - // credits for many parts of this code to jwklong owo - - function span(text) { - let el = document.createElement('span') - el.innerHTML = text - el.style.display = 'hidden' - el.style.whiteSpace = 'nowrap' - el.style.width = '100%' - el.style.textAlign = 'center' - return el - } - - - /** - * A complex number - * - * @class ComplexNumberType - * @typedef {ComplexNumberType} - */ - class ComplexNumberType { - customId = "reisenComplexNumber"; - - - /** - * Creates an instance of ComplexNumberType. - * - * @constructor - * @param {number} [real=0] Real part - * @param {number} [imaginary=0] Imaginary part - * @param {number} [modulus] Modulus in case you're working with polars - * @param {number} [angle] Angle in case you're working with polars, in degrees - */ - constructor(real = 0,imaginary = 0, modulus, angle) { - // console.log(modulus, angle) - if(!(typeof modulus == "undefined" || typeof angle == "undefined")) { - this._fromPolar = true; - this._modulus = modulus; - this._angle = clampAngleDegrees(angle); - - switch (this._angle) { - case 360: - case 0: - this.real = 0 ; - this.imaginary = modulus; - break; - - case 90: - this.real = modulus; - this.imaginary = 0; - break; - - case 180: - this.real = 0; - this.imaginary = -modulus; - break; - - case 270: - this.real = -modulus; - this.imaginary = 0; - break; - - default: - - this.real = (isNaN(real) | real == 0) ? modulus * Degrees.cos(angle) : real; - this.imaginary = (isNaN(imaginary) | imaginary == 0) ? modulus * Degrees.sin(angle) : imaginary; - break; - } - } else { - this._fromPolar = false; - this._modulus = null; - this._angle = null; - - this.real = isNaN(real) ? 0 : real; - this.imaginary = isNaN(imaginary) ? 0 : imaginary; - } - } - - static toComplex(u) { - if (u instanceof ComplexNumberType) { - if(u._fromPolar) { - return new ComplexNumberType(u.real, u.imaginary, u.absolute, clampAngleDegrees(u.argument)); - } else { - return new ComplexNumberType(u.real, u.imaginary); - } - } - // if (u instanceof VectorType) return new ComplexNumberType(u.x, u.y); - if (u instanceof Array) { - if (u.length == 4) { - return new ComplexNumberType(u[0], u[1], u[2], u[3]) - } - - if (u.length == 2) { - return new ComplexNumberType(u[0], u[1]) - } - }; - if (typeof u == "number") { - return new ComplexNumberType(u); - } - if (String(u).split(',')) { - const s = String(u).split(','); - return new ComplexNumberType(Scratch.Cast.toNumber(s[0]), Scratch.Cast.toNumber(s[1])) - } - return new ComplexNumberType(0, 0) - } - - - /** - * Support for the Jwklong Array Handler! - * - * @returns {string} - */ - jwArrayHandler() { - return 'Complex' - } - - /** - * Casts the complex number as a string, choose whether to use rectangular or polar form - * - * @param {boolean} [polarForm=false] Choose whether to convert it as polar form when casting to string - * @returns {string} - */ - toString(polarForm = false) { - if(polarForm) { - return `${this.absolute}∠${this.argument}°`; - } else { - return parseComplexToString(this.real,this.imaginary) - } - } - - toMonitorContent = () => span(this.toString()) - - toReporterContent() { - let root = document.createElement('div') - root.textContent = parseComplexToString(this.real,this.imaginary); - return root - } - - /** - * Returns the absolute value or modulus of a complex number - * @returns {number} - */ - get absolute() { - if(this._fromPolar) { - return this._modulus - } else { - return Math.hypot(this.real, this.imaginary) - } - } - - /** - * Returns the argument or phase of a complex number in radians - * @returns {number} - */ - get argument() { - if(this._fromPolar) { - return this._angle - } else { - return Degrees.atan2(this.real, this.imaginary) - } - } - - /** @returns {ComplexNumberType} */ - get conjugate() { - return new ComplexNumberType(this.real,-this.imaginary) - } - - - toJSON() { - return { - real: this.real, - imaginary: this.imaginary - } - } - - toArray() { - return [ this.real, this.imaginary ] - } - - - /** - * Creates a complex number given it's polar form - * - * @static - * @param {number} absolute The absolute value, or modulus of the original number - * @param {number} argument The angle, in degrees - * @returns {ComplexNumberType} - */ - static fromPolar(absolute, argument) { - const real = absolute * Degrees.cos(argument) - const imaginary = absolute * Degrees.sin(argument) - return new ComplexNumberType(real, imaginary, absolute, clampAngleDegrees(argument)) - } - } - - const ComplexNumber = { - Type: ComplexNumberType, - Block: { - blockType: Scratch.BlockType.REPORTER, - blockShape: Scratch.BlockShape.BUMPED, - forceOutputType: "ComplexNumber", - disableMonitor: true - }, - - Argument: { - shape: Scratch.BlockShape.BUMPED, - check: [ "ComplexNumber" ] - }, - - /** - * Serializer for this type - * - * @param {ComplexNumberType} z Unserialized - * @returns {{}} - */ - Serializer(z) { - if(z._fromPolar) { - return [z.real, z.imaginary, z.absolute, z.argument]; - } else { - return [z.real, z.imaginary]; - } - }, - - - /** - * Deserializer for this type - * - * @param {[number,number]|[number,number,number,number]} z Serialized - * @returns {ComplexNumberType} - */ - Deserializer(z) { - if(z.length == 4) { - return new ComplexNumber.Type(z[0],z[1],z[2],z[3]) - } else { - return new ComplexNumber.Type(z[0],z[1]) - } - } - } - - class ComplexNumberExtension { - constructor() { - Scratch.vm.reisenComplexNumber = ComplexNumber, - // Scratch.vm.reisenComplexPolar = ComplexPolar, - Scratch.vm.runtime.registerSerializer( - "reisenComplexNumber", - ComplexNumber.Serializer, ComplexNumber.Deserializer - ) - - this.formatMessage = function (id) { - return Scratch.translate({ id: id, default: id }); - }; - - this.formatEveryBlock = function (blocks) { - // console.log("Before") - // console.log(blocks) - return blocks - // return blocks.map(block => { - // console.log("Loop") - // console.log(block) - // block.text = Scratch.translate({id: block.text, default: block.text}); - // console.log("Next") - // console.log(block.text) - // return block.text - // }) - } - } - - getInfo() { - return { - id: "reisenComplexNumber", - name: this.formatMessage("Complex Numbers"), - description: this.formatMessage("Complex Number Type for do complex analysis functions, better implementation than the one made by jwklong in Mathemathics extension lmao"), - color1: "#ffdd02", - blockText: "#000000", - blocks: [ - { - opcode: "realToComplex", - text: this.formatMessage("complex number from [REAL]"), - arguments: { - REAL: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: 1 - } - }, - ...ComplexNumber.Block - }, - { - opcode: "imaginaryToComplex", - text: this.formatMessage("complex number from [IMAGINARY]i"), - arguments: { - IMAGINARY: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: 1 - } - }, - ...ComplexNumber.Block - }, - { - opcode: "newComplex", - text: this.formatMessage("complex number [REAL] + [IMAGINARY]i"), - arguments: { - REAL: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: 1 - }, - IMAGINARY: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: 1 - } - }, - ...ComplexNumber.Block - }, - { - opcode: "newComplexFromPolar", - text: this.formatMessage("complex number modulus: [R] phase: [PHASE]"), - arguments: { - R: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: 1 - }, - PHASE: { - type: Scratch.ArgumentType.ANGLE, - defaultValue: 45 - } - }, - ...ComplexNumber.Block - }, - { - opcode: "newComplexFromPolar2", - text: this.formatMessage("complex number modulus: 1 phase: [PHASE]"), - arguments: { - PHASE: { - type: Scratch.ArgumentType.ANGLE, - defaultValue: 45 - } - }, - ...ComplexNumber.Block - }, - "---", - { - opcode: "getRealPart", - text: this.formatMessage("real part [A]"), - arguments: { - A: ComplexNumber.Argument - }, - blockType: Scratch.BlockType.REPORTER - }, - { - opcode: "getImaginaryPart", - text: this.formatMessage("imaginary part [A]"), - arguments: { - A: ComplexNumber.Argument - }, - blockType: Scratch.BlockType.REPORTER - }, - { - opcode: "getAbsolute", - text: this.formatMessage("absolute value [A]"), - arguments: { - A: ComplexNumber.Argument - }, - blockType: Scratch.BlockType.REPORTER - }, - { - opcode: "getArgument", - text: this.formatMessage("phase [A]"), - arguments: { - A: ComplexNumber.Argument - }, - blockType: Scratch.BlockType.REPORTER - }, - "---", - { - opcode: "add", - text: this.formatMessage("[A] + [B]"), - arguments: { - A: ComplexNumber.Argument, - B: ComplexNumber.Argument - }, - ...ComplexNumber.Block - }, - { - opcode: "subtract", - text: this.formatMessage("[A] - [B]"), - arguments: { - A: ComplexNumber.Argument, - B: ComplexNumber.Argument - }, - ...ComplexNumber.Block - }, - { - opcode: "multiply", - text: this.formatMessage("[A] x [B]"), - arguments: { - A: ComplexNumber.Argument, - B: ComplexNumber.Argument - }, - ...ComplexNumber.Block - }, - { - opcode: "divide", - text: this.formatMessage("[A] / [B]"), - arguments: { - A: ComplexNumber.Argument, - B: ComplexNumber.Argument - }, - ...ComplexNumber.Block - }, - "---", - { - opcode: "conjugate", - text: this.formatMessage("conjugate [A]"), - arguments: { - A: ComplexNumber.Argument - }, - ...ComplexNumber.Block - }, - { - opcode: "multiplyConjugate", - text: this.formatMessage("multiply [A] with its conjugate"), - arguments: { - A: ComplexNumber.Argument - }, - ...ComplexNumber.Block - }, - { - opcode: "reciprocal", - text: this.formatMessage("reciprocal [A]"), - arguments: { - A: ComplexNumber.Argument - }, - ...ComplexNumber.Block - }, - "---", - { - opcode: "polarToComplex", - text: this.formatMessage("[POLAR] to rectangular form"), - arguments: { - POLAR: ComplexNumber.Argument - }, - blockType: Scratch.BlockType.REPORTER - }, - { - opcode: "complexToPolar", - text: this.formatMessage("[COMPLEX] to polar form"), - arguments: { - COMPLEX: ComplexNumber.Argument - }, - blockType: Scratch.BlockType.REPORTER - }, - "---", - { - opcode: "multiply2", - text: this.formatMessage("multiply [A] with [B] using the polar form"), - arguments: { - A: ComplexNumber.Argument, - B: ComplexNumber.Argument - }, - ...ComplexNumber.Block - }, - { - opcode: "divide2", - text: this.formatMessage("divide [A] with [B] using the polar form"), - arguments: { - A: ComplexNumber.Argument, - B: ComplexNumber.Argument - }, - ...ComplexNumber.Block - }, - "---", - { - opcode: "power", - text: this.formatMessage("[A] ^ [B]"), - arguments: { - A: ComplexNumber.Argument, - B: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: 2 - }, - }, - ...ComplexNumber.Block - }, - { - opcode: "squareRoot", - text: this.formatMessage("squareroot of [A]"), - arguments: { - A: ComplexNumber.Argument, - }, - ...ComplexNumber.Block - }, - { - opcode: "power2", - text: this.formatMessage("[A] ^ [B] using the polar form"), - arguments: { - A: ComplexNumber.Argument, - B: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: 2 - }, - }, - ...ComplexNumber.Block - }, - { - opcode: "nRoot", - text: this.formatMessage("[B]th root of [A] using the polar form"), - arguments: { - A: ComplexNumber.Argument, - B: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: 2 - }, - }, - ...ComplexNumber.Block - }, - "---", - { - opcode: "exponential", - text: this.formatMessage("e^[B]i pi"), - arguments: { - B: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: 1 - }, - }, - ...ComplexNumber.Block - }, - { - opcode: "exponential2", - text: this.formatMessage("e^[B]i"), - arguments: { - B: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: 1 - }, - }, - ...ComplexNumber.Block - }, - { - opcode: "naturalLogarithm", - text: this.formatMessage("ln [A]"), - arguments: { - A: ComplexNumber.Argument, - }, - ...ComplexNumber.Block - }, - // { - // opcode: "multiply", - // text: this.formatMessage("multiply [A] with [B] in polar form"), - // arguments: { - // A: ComplexNumber.Argument, - // B: ComplexNumber.Argument - // }, - // ...ComplexNumber.Block - // }, - { - opcode: "quadraticEquation", - text: this.formatMessage("solutions of equation [A]x^2 + [B]x + [C] = 0"), - arguments: { - A: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: 1 - }, - B: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: 0 - }, - C: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: 1 - }, - }, - - blockType: Scratch.BlockType.REPORTER, - blockShape: Scratch.BlockShape.SQUARE, - disableMonitor: true - }, - { - opcode: "quadraticEquation2", - text: this.formatMessage("[SOLUTION] solution of equation [A]x^2 + [B]x + [C] = 0"), - arguments: { - A: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: 1 - }, - B: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: 0 - }, - C: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: 1 - }, - SOLUTION: { - type: Scratch.ArgumentType.STRING, - menu: 'SOLUTIONS' - } - }, - ...ComplexNumber.Block - }, - { - opcode: "roots", - text: this.formatMessage("roots of equation x^[A] - [B] = 0"), - arguments: { - A: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: 1 - }, - B: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: 0 - }, - }, - - blockType: Scratch.BlockType.REPORTER, - blockShape: Scratch.BlockShape.SQUARE, - disableMonitor: true - }, - { - opcode: "roots2", - text: this.formatMessage("roots of equation [C]x^[A] - [B] = 0"), - arguments: { - A: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: 1 - }, - B: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: 0 - }, - C: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: 1 - }, - }, - - blockType: Scratch.BlockType.REPORTER, - blockShape: Scratch.BlockShape.SQUARE, - disableMonitor: true - }, - { - opcode: "roots3", - text: this.formatMessage("[D]th root of equation x^[A] - [B] = 0"), - arguments: { - A: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: 1 - }, - B: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: 0 - }, - D: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: 1 - }, - }, - ...ComplexNumber.Block - }, - { - opcode: "roots4", - text: this.formatMessage("[D]th root of equation [C]x^[A] - [B] = 0"), - arguments: { - A: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: 1 - }, - B: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: 0 - }, - C: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: 1 - }, - D: { - type: Scratch.ArgumentType.NUMBER, - defaultValue: 1 - }, - }, - ...ComplexNumber.Block - }, - ], - menus: { - SOLUTIONS: { - acceptReporters: false, - items: [ - { - text: this.formatMessage("positive"), - value: "positive" - }, - { - text: this.formatMessage("negative"), - value: "negative" - }, - ] - } - } - } - } - - /** - * Creates a new complex number given the real and imaginary part - * - * @param { number } args.REAL - * @param { number } args.IMAGINARY - * @returns {ComplexNumberType} - */ - newComplex(args) { - const real = Scratch.Cast.toNumber(args.REAL) - const imaginary = Scratch.Cast.toNumber(args.IMAGINARY) - - return new ComplexNumberType(real,imaginary); - } - - - /** - * Given the polar form creates a complex - * - * @param { number } args.R - * @param { number } args.PHASE - * @returns {ComplexNumberType} - */ - newComplexFromPolar(args) { - const modulus = Scratch.Cast.toNumber(args.R) - const phase = Scratch.Cast.toNumber(args.PHASE); - - return ComplexNumberType.fromPolar(modulus,phase); - // z = r(cos a + i sin a) - // const real = modulus * Math.cos(phase); - // const imaginary = modulus * Math.sin(phase); - - } - - /** - * Given the phase creates a complex assuming modulus is 1 - * - * @param { number } args.PHASE - * @returns {ComplexNumberType} - */ - newComplexFromPolar2(args) { - const phase = Scratch.Cast.toNumber(args.PHASE); - - return ComplexNumberType.fromPolar(1,phase); - // z = r(cos a + i sin a) - // const real = modulus * Math.cos(phase); - // const imaginary = modulus * Math.sin(phase); - } - - - /** - * Converts a single real number into a complex type - * - * @param { number } args.REAL - * @returns {ComplexNumberType} - */ - realToComplex(args) { - return new ComplexNumberType(args.REAL,0) - } - - /** - * Converts a sole imaginary number into a complex type - * - * @param { number } args.IMAGINARY - * @returns {ComplexNumberType} - */ - imaginaryToComplex(args) { - return new ComplexNumberType(0,args.IMAGINARY) - } - - getRealPart(args) { - return ComplexNumberType.toComplex(args.A).real - } - - getImaginaryPart(args) { - return ComplexNumberType.toComplex(args.A).imaginary - } - - getAbsolute(args) { - return ComplexNumberType.toComplex(args.A).absolute - } - - getArgument(args) { - return ComplexNumberType.toComplex(args.A).argument - } - - conjugate(args) { - return ComplexNumberType.toComplex(args.A).conjugate - } - - add(args) { - const A = ComplexNumberType.toComplex(args.A); - const B = ComplexNumberType.toComplex(args.B); - - return new ComplexNumberType(A.real + B.real, A.imaginary + B.imaginary); - } - - subtract(args) { - const A = ComplexNumberType.toComplex(args.A); - const B = ComplexNumberType.toComplex(args.B); - - return new ComplexNumberType(A.real - B.real, A.imaginary - B.imaginary); - } - - multiply(args) { - const A = ComplexNumberType.toComplex(args.A); - const B = ComplexNumberType.toComplex(args.B); - - return new ComplexNumberType( - A.real * B.real - A.imaginary * B.imaginary, - A.real * B.imaginary + A.imaginary * B.real - ); - } - - multiplyConjugate(args) { - const A = ComplexNumberType.toComplex(args.A); - - return new ComplexNumberType(A.real ** 2 + A.imaginary ** 2, 0); - } - - reciprocal(args) { - const A = ComplexNumberType.toComplex(args.A); - const u = A.real ** 2 + A.imaginary ** 2; - - - return new ComplexNumberType(A.real / u, -A.imaginary / u); - } - - divide(args) { - const A = ComplexNumberType.toComplex(args.A); - const B = ComplexNumberType.toComplex(args.B); - const u = B.real ** 2 + B.imaginary ** 2; - - - - return new ComplexNumberType( - (A.real * B.real + A.imaginary * B.imaginary) / u, - (A.imaginary * B.real - A.real * B.imaginary) / u - ); - } - - - /** - * Converts a polar number into a complex number - * - * @param {ComplexNumberType} args.POLAR - * @returns {ComplexNumberType} - */ - polarToComplex(args) { - const POLAR = ComplexNumberType.toComplex(args.POLAR); - - return POLAR.toString(); - } - - - complexToPolar(args) { - const COMPLEX = ComplexNumberType.toComplex(args.COMPLEX); - - return COMPLEX.toString(true); - } - - - multiply2(args) { - const A = ComplexNumberType.toComplex(args.A); - const B = ComplexNumberType.toComplex(args.B); - - return new ComplexNumberType( - A.real * B.real - A.imaginary * B.imaginary, - A.real * B.imaginary + A.imaginary * B.real - , A.absolute * B.absolute - , untransposeAngle(-(A.argument + B.argument) + 180) - ); - } - - divide2(args) { - const A = ComplexNumberType.toComplex(args.A); - const B = ComplexNumberType.toComplex(args.B); - const u = B.real ** 2 + B.imaginary ** 2; - - return new ComplexNumberType( - (A.real * B.real + A.imaginary * B.imaginary) / u, - (A.imaginary * B.real - A.real * B.imaginary) / u - , A.absolute / B.absolute - , untransposeAngle(-(A.argument - B.argument)) - ); - } - - power(args) { - const A = ComplexNumberType.toComplex(args.A); - const power = Math.round(Scratch.Cast.toNumber(args.B)); - - const firstReal = A.real, firstImaginary = A.imaginary; - let pair = [firstReal, firstImaginary]; - - if(power == 0) { - return new ComplexNumberType(1,0) - } - if(power == 1) { - return A - } - if(power == -1) { - const u = A.real ** 2 + A.imaginary ** 2; - return new ComplexNumberType(A.real / u, -A.imaginary / u); - } - - const absPower = Math.abs(power); - - for (let _ = 1; _ < absPower; _++) { - pair = [ - pair[0] * firstReal - pair[1] * firstImaginary, - pair[0] * firstImaginary + pair[1] * firstReal - ]; - } - - if(power < -1) { - const u = pair[0] ** 2 + pair[1] ** 2; - return new ComplexNumberType(pair[0] / u, -pair[1] / u); - - } - - return new ComplexNumberType( - pair[0], pair[1] - ); - } - - - squareRoot(args) { - const A = ComplexNumberType.toComplex(args.A); - const r = Math.hypot(A.real, A.imaginary); - - return new ComplexNumberType( - Math.sqrt(1/2 * (r + A.real)), - (A.imaginary >= 0 ? 1 : -1) * Math.sqrt(1/2 * (r - A.real)), - ); - } - - power2(args) { - const A = ComplexNumberType.toComplex(args.A); - const power = Scratch.Cast.toNumber(args.B); - - if(power == 0) { - return new ComplexNumberType(1,0) - } - if(power == 1) { - return A - } - - const r = A.absolute ** power; - const phi = A.argument * power; - - return new ComplexNumberType( - r * Degrees.cos(phi), - r * Degrees.sin(phi), - r, phi - ); - } - - nRoot(args) { - const A = ComplexNumberType.toComplex(args.A); - const subRadical = Scratch.Cast.toNumber(args.B); - - if(subRadical == 0) { - return Infinity - } - if(subRadical == 1) { - return A - } - - const r = subRadical == 2 ? Math.sqrt(A.absolute) : (A.absolute ** (1/subRadical)); - const phi = A.argument / subRadical; - - return new ComplexNumberType( - r * Degrees.cos(phi), - r * Degrees.sin(phi), - r, phi - ); - } - - // power3(args) { - // const A = ComplexNumberType.toComplex(args.A); - // const power = Math.round(Scratch.Cast.toNumber(args.B)); - - // if(power == 0) { - // return new ComplexNumberType(1,0) - // } - // if(power == 1) { - // return A - // } - - // const r = A.absolute ** n; - // const phi = A.argument * n; - - // return new ComplexNumberType( - // undefined, undefined, - // r * Degrees.cos(phi), - // r * Degrees.sin(phi) - // ); - // } - exponential(args) { - const radians = Scratch.Cast.toNumber(args.B); - if(radians == 0) { - return new ComplexNumberType(1,0); - } - if(radians == 1) { - return new ComplexNumberType(-1,0,); - } - - const real = Math.cos(radians * Math.PI); - const imaginary = Math.sin(radians * Math.PI); - - return new ComplexNumberType(real, imaginary, 1, radianToDegrees(radians * Math.PI)) - } - exponential2(args) { - const radians = Scratch.Cast.toNumber(args.B); - if(radians == 0) { - return new ComplexNumberType(1,0); - } - if(radians == Math.PI) { - return new ComplexNumberType(-1,0); - } - - const real = Math.cos(radians); - const imaginary = Math.sin(radians); - - return new ComplexNumberType(real, imaginary, 1, radianToDegrees(radians)) - } - naturalLogarithm(args) { - const A = ComplexNumberType.toComplex(args.A); - - if(A.real == 0 && A.imaginary == 0) { - return NaN; - } - if(A.imaginary == 0) { - if(A.real > 0) { - return new ComplexNumberType(Math.log(A.real)); - } else { - return new ComplexNumberType(Math.log(Math.abs(A.real)),Math.PI); - } - } - if(A.real == 0) { - if(A.imaginary > 0) { - return new ComplexNumberType(Math.log(A.imaginary), Math.PI / 2); - } else { - return new ComplexNumberType(Math.log(Math.abs(A.imaginary)), -Math.PI / 2); - } - } - - const r = Math.log(A.absolute); - const arg = A.argument * Math.PI; - - return new ComplexNumberType(r,arg); - } - - quadraticEquation(args) { - const a = Math.round(Scratch.Cast.toNumber(args.A)); - const b = Math.round(Scratch.Cast.toNumber(args.B)); - const c = Math.round(Scratch.Cast.toNumber(args.C)); - - if(a == 0) { - throw new Error("Quadratic component can't be 0"); - } - - const det = b ** 2 - 4 * a * c; - let solutions = []; - - if(det > 0) { - const positive = ( -b + Math.sqrt(det)) / (2 * a); - const negative = ( -b - Math.sqrt(det)) / (2 * a); - - solutions = [ new ComplexNumberType(positive,0), new ComplexNumberType(negative,0) ]; - } - - if(det == 0) { - const unique = ( -b ) / (2 * a); - - solutions = [ new ComplexNumberType(unique,0), new ComplexNumberType(unique,0) ]; - } - - if(det < 0) { - const positive = new ComplexNumberType(-b / (2 * a), Math.sqrt(Math.abs(det)) / (2 * a)); - const negative = new ComplexNumberType(-b / (2 * a), -Math.sqrt(Math.abs(det)) / (2 * a)); - - solutions = [ positive, negative ]; - } - - return solutions; - } - quadraticEquation2(args) { - const a = Math.round(Scratch.Cast.toNumber(args.A)); - const b = Math.round(Scratch.Cast.toNumber(args.B)); - const c = Math.round(Scratch.Cast.toNumber(args.C)); - - - const det = b ** 2 - 4 * a * c; - let solutions = []; - - if(det > 0) { - const positive = ( -b + Math.sqrt(det)) / (2 * a); - const negative = ( -b - Math.sqrt(det)) / (2 * a); - - solutions = [ new ComplexNumberType(positive,0), new ComplexNumberType(negative,0) ]; - } - - if(det == 0) { - const unique = ( -b ) / (2 * a); - - solutions = [ new ComplexNumberType(unique,0), new ComplexNumberType(unique,0) ]; - } - - if(det < 0) { - const positive = new ComplexNumberType(-b / (2 * a), Math.sqrt(Math.abs(det)) / (2 * a)); - const negative = new ComplexNumberType(-b / (2 * a), Math.sqrt(Math.abs(det)) / (2 * a)); - - solutions = [ positive, negative ]; - } - - switch (args.SOLUTION) { - case "positive": - return solutions[0]; - - case "negative": - return solutions[1]; - - default: - break; - } - } - - roots(args) { - const a = Math.round(Math.abs(Scratch.Cast.toNumber(args.A))); - const b = Scratch.Cast.toNumber(args.B); - - if(a == 0) { - return NaN; - } - if(a == 1) { - return [ ComplexNumberType.toComplex(a) ]; - } - - let roots = []; - - const r = a == 2 ? Math.sqrt(b) : (b ** (1/a)); - for (let k = 0; k < a; k++) { - - const phi = (0 + k * 360) / a; - - roots.push(new ComplexNumberType( - r * Degrees.cos(phi), - r * Degrees.sin(phi), - r, phi - )); - } - - return roots; - } - - roots2(args) { - const a = Math.round(Math.abs(Scratch.Cast.toNumber(args.A))); - const b = Scratch.Cast.toNumber(args.B); - const c = Scratch.Cast.toNumber(args.C); - - if(a == 0) { - return NaN; - } - if(a == 1) { - return [ ComplexNumberType.toComplex(a) ]; - } - - let roots = []; - - const r = a == 2 ? Math.sqrt(b/c) : ((b/c) ** (1/a)); - for (let k = 0; k < a; k++) { - - const phi = (0 + k * 360) / a; - - roots.push(new ComplexNumberType( - r * Degrees.cos(phi), - r * Degrees.sin(phi), - r, phi - )); - } - - return roots; - } - - roots3(args) { - const a = Math.round(Math.abs(Scratch.Cast.toNumber(args.A))); - const b = Scratch.Cast.toNumber(args.B); - - const c = Scratch.Cast.toNumber(args.C); - - if(a == 0) { - return NaN; - } - if(a == 1) { - return [ ComplexNumberType.toComplex(a) ]; - } - - let roots = []; - - const r = a == 2 ? Math.sqrt(b) : (b ** (1/a)); - - const phi = (0 + d * 360) / a; - - return new ComplexNumberType( - r * Degrees.cos(phi), - r * Degrees.sin(phi), - r, phi - ); - } - - roots4(args) { - const a = Math.round(Math.abs(Scratch.Cast.toNumber(args.A))); - const b = Scratch.Cast.toNumber(args.B); - - const c = Scratch.Cast.toNumber(args.C); - - if(a == 0) { - return NaN; - } - if(a == 1) { - return [ ComplexNumberType.toComplex(a) ]; - } - - let roots = []; - - const r = a == 2 ? Math.sqrt(b/c) : ((b/c) ** (1/a)); - - const phi = (0 + d * 360) / a; - - return new ComplexNumberType( - r * Degrees.cos(phi), - r * Degrees.sin(phi), - r, phi - ); - } - } - Scratch.extensions.register( new ComplexNumberExtension() ) -})(Scratch) diff --git a/static/images/salagata/complexDocs/angleEquivalency.png b/static/images/salagata/complexDocs/angleEquivalency.png new file mode 100644 index 0000000000000000000000000000000000000000..dcede66e3252ab7f83ba93268d750d43d39b2b5d GIT binary patch literal 43266 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 RW($8os9dZf4gm&t4;mao(1GC2cXIFh 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 literal 0 HcmV?d00001 diff --git a/static/images/salagata/complexDocs/scratchAngles.png b/static/images/salagata/complexDocs/scratchAngles.png new file mode 100644 index 0000000000000000000000000000000000000000..92e8a98d2dd25b6d5a828766c212a84362ad5eaf GIT binary patch literal 11355 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 Z!Q3cgPyqw@f4|;<$w?_mR*M@2{U1+;y#W9K literal 0 HcmV?d00001 diff --git a/static/images/salagata/complex_placeholder.svg b/static/images/salagata/complex_placeholder.svg new file mode 100644 index 000000000..8122f4b78 --- /dev/null +++ b/static/images/salagata/complex_placeholder.svg @@ -0,0 +1 @@ +e = cos(π) + i sin(π) z - a = 0nax + bx + c = 02θr1∠ɸz0z1z2z3z4z5z6ɸ-1(5+√15 i)+(5-√15 i) = 10(5+√15 i)x(5-√15 i) = 40a+b = 10axb = 40x + 10x - 40 = 02a + bir∠θ \ No newline at end of file diff --git a/static/images/salagata/reisenComplex_placeholder.svg b/static/images/salagata/reisenComplex_placeholder.svg deleted file mode 100644 index 641dd1b98..000000000 --- a/static/images/salagata/reisenComplex_placeholder.svg +++ /dev/null @@ -1 +0,0 @@ -ie = cos(x)+isin(x)ixax +bx+c=0x - a=0n2 From 5ededeec1efde26082bd5d27996a741b7956c7ac Mon Sep 17 00:00:00 2001 From: salagata <122649005+salagata@users.noreply.github.com> Date: Fri, 4 Sep 2026 23:18:53 -0500 Subject: [PATCH 7/9] Deleted complexdocs image files --- .../salagata/complexDocs/angleEquivalency.png | Bin 43266 -> 0 bytes .../salagata/complexDocs/complexPlaneAngles.png | Bin 13316 -> 0 bytes .../salagata/complexDocs/scratchAngles.png | Bin 11355 -> 0 bytes 3 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 static/images/salagata/complexDocs/angleEquivalency.png delete mode 100644 static/images/salagata/complexDocs/complexPlaneAngles.png delete mode 100644 static/images/salagata/complexDocs/scratchAngles.png diff --git a/static/images/salagata/complexDocs/angleEquivalency.png b/static/images/salagata/complexDocs/angleEquivalency.png deleted file mode 100644 index dcede66e3252ab7f83ba93268d750d43d39b2b5d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43266 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 RW($8os9dZf4gm&t4;mao(1GC2cXIFh zs@|_h6%2LebRX&NwfA0Yb<8_;1xz$jG&ndoOeIBGE#UJW_E{(t6n#*4)uB#rZX0PgUdsw#s7*%OS}79Bmb9 z50&Pnc3Jy@i>`|BB_AXmO&r3;iK!orOuYKmD&Igr2$Qqb)6gu0umFVYBZk4tI;+D6 z-E=9Z>su*C0lqtuDyvk~o`(zXC`-g}OsC|9ZdRid*VC%$B4EJ23v*W*yE=l?S&@BA zeh#B?VFN9)hXm{N&i{4ye;w);Q$JRoCY?rKeR+O3JyFiv8Tx!t-52z5{nvzn_1zii zCKZf3^Z4=Z;$m+C9Ssc)3v0dJS|u=0wA-JJi77+Soz@~a&bs)O3BqHaF+JW}{aUlt zR`=<(>2ESVCo2T6Un6D>xo!1E?(SOki+&l2tw+Q>!}cj}I$Ep=zugSS#<`$es4>mU z%p}3X>kSyGlr(pgG<7S{c0F3C`qq`sVY*PJUGn9z!D*vaFp(=prj!@*e79&CBIb8)wTl1c?T1fn z4${)ny>lfRG~kRIT~MsG6!hQw;xKNH@Sd z*_K7!ZBIB#91+K->|%w_lnBTeU`b_VGz&iSp+t&SV@WuOhz|_02YwFx>0f{5U-SJP zM!@+r+}wDG-@WzOs5u2xif1n`FFCp&X9ot5$Q=fMR(&99%mO!Ya7)8++2H^)$gUJ5 z)=SaQQGolIO5zjl_RbEE$FAaN(H8;Hw63nMRGEn<;2CCB(zskmZ*w=B!8-lL^`yl^nQn4)2^PzGj;-CkuPQ!=;1#TqJn z{BWau12fWTSzxRw(5FjFOMTY;N-PFqV+p6*R;Q*kdO)h6r)KA0T3BrO`XU(zIXOAZ z3f35Z=qf4L+>+5)QKL~%c!`#%<|OU^O{|Z!9NbFbF?U^ZafFRxwUV1jWhR~N@T(am z4Gj$-Q_wK0c16bM(Q7 zDnPi+M{TXG&2zt}M^lLhM(fkS8Crw8@vs8q@a)`Pr^f?LYe3>#SgIU`jdt_px~1n! zb(SG7Pc5np2$)o&qJKvn-$5w?57SUWHxCYCZh^UwDIRn`p2;Isfx*wuFLyg2B!{^4 zW)3Wk5ZFtRopTnck;Q927*B#p$_s9TNBFdWnAppRfaQeeR=bRtg`C0F{dkTZn3_>r zX0Lw7W<$EE2{a4>RtS2UxM|Q8fwSGtU_uXUwT-7!cC|3E2mez?$D;Dp#yS=lJ4qo>fgaDJx64fq{{cb=TYr>~{k*MndV2c!j?= znS(BkTbqr=h4{OqzRrM>kWl_K1;?S>!O9&nRCKHw9a0`H?Z_ZJjywjf?i4bv7 zQSw`PR4gocYn62N;!oU0edn%HGpI}v#M0UP&RA697z$^56WN}gp4QyUz#RD1>>O}) zsOy_alh2S}4M0h+UeCwGcpVQY7zBXFP=8)}dK3l05+{X-w=Cp*C%lzc-^@)Qqa0IH z)52mqmVk91`TG9q2$)Mgty1F8x0Z$ilJTdKe`{?fNLj#3eSHY{fm&L4`~!>C#&I!J z!7`N`$r16fJ30jg1;FSRwyJ7qL`f!)@^O0hy^Bfh{sos97l+=ARiRs9^EPDs)z^oc z({O_t6S3(}*ch-+i*1yS{cKo$2n0~&=iL+yIKF#T6v3>h7<@(x$n)PJDtCdUHc!TB zr3-llg%Hi#w!k~?@$B9Tj8rXWK`4)U zRpW(x441t*8*a19nnNlwasF&-YAUwGBaSvV^l)=Czx3~BPk4oHP$IcdK@v!_JNQXG zM>t2s2g}Z7sg_2N*Uab9d<(_q{`T(9!ke?HAnT%o zU~uf4ad1q9DzV_P_09m+3-iUWa3N&#>3peH9aQvBU>+*~M*(2Ndi6>(7!{jB)NK>Y z(pbX|@6+mzhlfXv?TJ*U2Rd3|(Jhy(yA0X}z}FkLYVem=F#q!`@#;533WVCk4weOZ zNlw-ILWR$>VP@j+%eBgJXZGdbozt!dmyNEVjEslN1MQ-0{Ra8cd+Io8ybZz(nT(OzHr?s-*1>`eUFyM0b6A9*sK zY0Og1uHB*r;D<=&je#iG^>qC~IsCJVa(D*6Gifmj7J2js5WIYt2O@r-g1GP5CJ^*? zSl=cqPIDaJ!g+FXN;=|gGQ4a>Q9a-8U@tw@MkgT&AdcTq$0C}MUI*baboQrDtgllx zi~xAv_3|7rTEx}l#(85okG@E6P0Gz5&yYeejD6yM@lO{;qLy*?oZ>Kua zRS;ejT>E=dkV6-{w*2oC6_3sMS0T?@`bg2h$l5m7^&fXVC{$)8gyfhAghNZ~VSTt8 zrYO{ zk7~G>Hz{Y<<}Yf{uRPY=q>U z8;sW=o1H-K(5J+HWQvQ6a~9V+%$|MebyeYIo;cpZdcgZ60{_MC?3~5>N1b8sEar_J zTleOh{4owS-d?N5S?EX#qj{&<79tfBDx34c#b-kT3r_QfsE0p!Ay@&ppv_MdHI;%( zN1F&o)TwvUh1T5l0Srp9+{)eG)pO9I`@SX4GZLzQx9?-(wY+wFe<`aN;T@!u@P?=b za+jcVOL)RSry-X2$V0gIs`T+xOZWj8w{%Ig#q-T`ap`S$gC(rTZD z+l8#{i6e6()o9sT+L@>LJCK&n+NIXLoj^%iejlx8i_{6SF2JD@Q3CG9To*{8tUqkBO}a?f7n=F#)#`n-?lZUw=ObB zsf=9C(Tl|d{)#tRveI;5YHKixP2Qp;C5<(9Ojay<3>=6=Ct`VD9bXmu_!wB7a9pE< zxhk&QbwfAvUQtTi{$~z;%}nfgEOkOavN4Ym{|)UyI12W6)y%MyW*V7KP@%CA{q?-$ zEv$KjRUoZ{h9bnGsBLvrX_sIbwJqT^{lJ~+8bTp*7tbtWG@A5)74JfCA(Q7dH*$KfT#&O&8J@-@*9;Y0m<5JTbUH5tY^r#GpKii$Sz5FuLH+EUci zabYn9&>nB%3?@w870Jv}bX8tt)ke&>pimXg8RBUYW~Dm`AI697ontQ}I-SbDqbo?g z)MZ6KV;)BGn;aKyY;D8KP%GZo$QQ?|pwq--`!JDi%Kr#?$zEK{1LFC2HM09ec!mX%J{f;BU4q+ci$mdXCGrpWXQi8#czB;d4He!f*1yL2{ zkB2|YfUJ!gCmcBx ztYd|!+2QTH3m=Mvk#R|bdT)Z1;qC2h*4v1%mM?{{iYz54CL52qzyjFdU zghhB{R=_)LhhSa64oo@D8U98@ME0J>>LUel5!$=s{DcZ3zmK7fR0`3GyP8o;A~G1g z5jZR{(vqJ^c#vB)ydCpQCW+Effx=axn3|0xTCJ!>%>NQx=6|`bNt_Dge>r@Pgh-h& zyi>B-xSrvUS8rb3#y3)^I~^;QES5wP(8sj`U;uH7i=b8e;%Rpz1xO&n+JwB1^?NG1Dk;Xt_Q<&@o0K-;w+%ikIw=;Ku#Cm4 zu-aW2kFS3y09CCr#8Y*c^}wsLv%Eo?;w5PHfvu9XZ1UJn{tJje)mTf8SaV2Os;`<;f{A`A|Nw^T@C6Re1mdU!N3$|R3US8_{_G)A#1*yiL z%k#a5xA+ht?#%SUZ`5RS=^j0}A?^~1^%Vyex1r|i^WS4HKpG)P=zH{)8XclEa=+ET zl8a$%to4%n=8wP=|K2H?0ZAJ-%Z?cx>B)O67cdcr$@CNAXiMV~{U!j^!e&sg_1G6p z#>V1=Q12(q4~j5KMyg1h5;PW`+}7;1?T!qQDbEJ9IxH^N_Z98A{BFuX<&xI^*wY9# zBX6~o;Gm;Jpd@zExIUbBT>(YO8smNMUn>YdrGML0lF{|ClFt_)sx^dohz9+T9r) zpi7P4snpWMfG-S%YzYFj(?{%#j@lm*J5UO5^heGmzfc+Ej%^fd%80_X^VAya&kb`U zSv36}TnAhwzLS?u_APmqFCBD3Z&mi37`3ED-0PDuoOdGF;DqDltJI+~>@-L~SDt?U z{{8@4vQ4Lu`0-#?PEmONy(xc)s+Q%*{`5B1(1W{Qr{18LMlv$Lm;TR{ z%ElT>a*VGOiFttX%+u#jZWXi`2^r`w_LB&P|B$?Z9sVnZwfJb~-ClX_U4d&4cbr@uORX$x68m_}|QQ>UsdNBymP*a;| z+4SirUY(a2M%20qmdQ1uFO-hyY{*&|wzAs&gm`O3J-4@rmRoHdiJc4pdserfd3@>@qhmaCAO*Y!CpUp(9LA`Yc*Xg`AFl-yVt( zhAc?i*V}7hVXG9#TD|1rhr{9qjX^iY_ou$J4Qjj?#S8Mj}?V#A^B!Z(vXd~0_oWh*+*~OfoNO8fHXlaFjCH+qK zUa$c@6xRd1d`jnDkk1L$TWqWx3QOhRFFBRF1PqgF-7V@#`|2;u$E?-8-jp6wmY6<1$c>UIMW z6NkZIuyPtpSu(CSUUO@4~gS~iAQ6PqrimQ{k(W#-sdm@97l&YprkU@yObzk`}EJZEGa@l2rD2dKZY8fj?R zik%A7fzxF%65sB>fTXkIkvpRPZ56%|?S>k%Jt7f(8Z# z_Gxm8y@OF5NsVQ4rphAJN}4}MEz1I`tv6@al*qY9jqkiLIh)YjWG&ub4a5-{{>FoF zhIequYoCCWMlst7vPGO3Z?YiKNql{)tZk`Y7T)ge<_#V7pGc}gFjE61As%Z&e)T<$ zI}PW8*+E*2>FbeyY?;RFcnGb2Is56U9VYiH3~i7Ir|e|aSK_|JzC;0z&rpy73$4!?AdFWg2U~UN&S}i z&d$yte@b-TngZfgOg7ncbfNKfcmT(_t*phVW773vWobD$IK0o0 zIYzA_Q!7KK>nW3TJZ21w;9v=40^VNpumqg9ES!Ft{2fOj(U+c*5Ui6=bmu^=MnbYm zS%tNXlvJ$hMkM)iwT|>eo>cUq!Mwxthi`JuX?6Cse)TMG{uBa4x#EMIa3J^7{S`n> zq-x*l?7l{en}FSuD|J#N6?qnh`s$wv+X~#oW5E2$r2HkdUQVw*{^v?u4XzvF*JA;~ zrHR&>6`%o-QVwH98r>)gy)(s5*%nCYaEy(a;KCoEpV;nvMwX9F@c2iei&x;H8;4)G zTA%z?#?V>??53du1I4^jJ>Pj+kId~bJ^tq@o}V6SOx!hc50Z@G42W0nC}nf76jyka zI}jir9eFo{fe4oR8s?_7rp$R`gaP}Jm=BDA?&33;o?_)KVH`kgO_3)Hm#Rk_%%%(< z<}YroGehuH780n@vr@YE>{fnByvX2DLfn5vH|1opSxZnWsco?-B_J;yR~M0gP?!yd zR|-~UcV@gZ*j<#|){86}NUn8N;`d=Igp{(JkGgm`5WF&x|8ZER9Nf3_8(btXV6WPP z+Xk?R`}=kYo~UxQwNx0e$Si1x>d4j8h{$Ouy#N*@w?S#+R53+zmNsKa$X%5$gu`-p zu&&5nVp~8y;Wflpi2Bv zs2dt)anBXlA>Xt_i9CyssM4$_C?)8*GNuL&P$XSq9*D<5aaat{m_;d*B4$6Lf**jQ zl4RBEU5;dfXgk!a^4n_sR_W|>ZGpLpSBfMZ3*>bG&FUDw(`^1(0dq{}P?hbGNDzrWv7p9C7HwU^}p_>!TAw;zXVRZvjKURFZZ)zHCDXUB~~Z$Gy$P!FK()uL&w?RqU+yoKAMFI4RSOrJpZ^o2T}as?{3dN}T;Dgy>Krth*|I!0= zW%gC=6ikmvT4ji+KQ%I16cVf0^}#Zp|ORH4Z|bPAw6QwAkf5D?Fvla zl)%n~EBOMDMN1^fZ&-#4@b^JV#@`{I#C{%`_boS0Y^`b{{XQA^1i)=jfUQbT6{PeT(OVLaJo(R5o^AI!P2i=&V&5VP{gbRJa4#qAh4g@cgo$nvV{SmtV-uUu zB1E2~Q#nJ*wHbZFtsl5nF2>F;K!Bi;Rb9QH5wI++WJRqN zdu{LzLpM!Iize`u9N0)Gl<5~D*$7@d&#l522W@|kB=MJ}sGKq+Qjfm@Qo8%ggWFqQ zPkYYO6Dzm$ic}G5dwh~}vcHvd-RxXh{|$POAXWp8!&BriB%WEga1k0CUrC4yYB=32 zAhi`np?<7@?s^)Cu1;Kl>2uuEQd9eLeHIM@_VQ*#u@z_4PTgcFfkGGv(e2hmlI&x- zYZKRC`n4p+WW{#x6T>zS>$Q4x>U5;OwXeYGBz}{VlM4%nMjFEDPJ|?HiD5Ru0KlJx z(VYW4zPr1-@GgNo;ZEvC(VXs&*S!f*h#5WvgEJfkqylkY3c4a+mAAzW@v~_v?6X1c)uCLjRb@K7P@OGf@ zgXz8Obh7t0H{qr$2yi_L2B5~-2#~OeEg-tQd!?|u)fbfqB%LxQ=2msYt&YGlPbmtF zuQqk=Fc346LS=4kYpeUS7Rj4meoxzq04TE9u>a2m=y=tCq$E!U5NUNhW(u24Y^<13yyO(R=U4mZW)7odiE zOZdQyq@}~!_`bGWA7c2d8|H6>MFarv*FRk2yX!`mZiU`FO$}!~Dk-d2j9SU|%;3X^ zzOcoAAoH4iHS*s`42N?yguFawWDrxFqtV5=ZL#kI!+?;dNFVlQXWve}gmHgi4&w3Z zHEe|{X_sNrL}J*W5=JDfJo|0=40Q7elMIl)*PRTc_|d<)(YWw7neHM_ zZ0ExWX?lsRlprmYMDW+#$*1$M+zRfQZQ#@sY{jhOQ*JxhbXYpaoPbmG^;LrNhWG^( zgile&&5Pa~AS)}YHG9+<$(?FdZGeRGpU#Q*_m*n%I=dxsW324$mrFE;Fv*GMij+nn zZ)T63JSl!z02$Cfd2PPpJ;xUdW(@P5PA3%lwi;7c-6~QFKLZB`bt|j$c#@ek){^l| zZL^*aJUTSJ(MX6l+*C>Q-X0#N9eP?@SDK5E!+9|o=^wqlqX|?T?4~AgJD8R>fHpA| z(ILp}6_1GMBaWq5kBwT3jX|%cM;q`ER$6+QK<1pq*KmGKXz#5b$txl8G$oN@I~lRw zz+uwgD`0-P{}r9c%^nSuOF~6OWot|GBeS?oJnc;+{7pc~aA;)FqNr9yg2zqlTpV`s zara2)cb)$R7oG8o{Vt10wWs@S7TrpR#lbK>^Ib*S9m*nR6CGUj^{0oa4^7Hnh3Cs{ zCVmWkHVr%rRqOjy8)hNwJud819bK!*E=Q{gsI|na2?c@AEEE5$f+h05v%fwTV@?VU z5!iJRajExJv3pxV ziNk;*3U~*8woY(boY-L8AY=2V{Ap=5)vn+Az6D8TN=xRZOrrPrc)8RVqyM2Vda5(H z(^t+yLxXeRyo^HjIuaeNwR@!%m%Id*4u?!ETDCwwfwZk1?Ig%^g21ub?etJBFc-%7 z)Xk`yU3z-*7{m;u#v{#g=!+a()OUAE+9iZFlhp=1l?n}geQoq*yo|JAy<(OR*Of9+ zqnK2%=`#Fut78dhCm`Pb-qrh3_sinz{RAG2zs;UZIQb6&Y06~Ee8t;GrO7bP$qTzT z3?GJk+lBYnH_mx%4RtHNT%C54JX{l$y@nC}QA+WsF;TnQc=4zX{{3=}`hkCiWSaj(8@1AThkR%CwELDpm^z%9GGHm#6cQat& z#G>07yyn-B`lrsaqT5%~eu@$$2g0S9b0Rqdx2bLrgN z|H761s^H7>Q~c9I+#+U$*v%m3FTJW*#!Pq={f#Ea^u0ty>JFdNbxD5ImFJ5;Q{niG zFBqh}sOhP$>~%uWr+#I;kSJ|Cpl2itFNyDI9BEhvWHsFgkgp}GQkJt{-pUx!n>Dd` ze{8JQtw7z7WgoC`AN%r_@9`>ecvz7-0MKBe*MQ6#46x|<2XY2k=2&js%~5b0M9oRs z6^dsX{S~?QSW_!G9$W5MRt_B>-eUScoe8twp{WC=r?s_)MDmd~_d>f&Jw2C=;EG_7 zF>f5GhD&3yRI^XBAqBO!ukU@0$qCKN<>CAhpiSG^1HAhG7Rs|UIsIDu^fiK{>P?g1 z;w#NbYL(EnH8!~X%vFAK%$C!d{j_5a!)5v(b|m)&EB54gG=W0Js{Z30A>`cb7r!e^ z)V+XvsK`$LB%?uFOK{4m3^N?0U2(>bWC2)Bc*g)CPZJBi3nA4~p6kVKv+d&LS#cI$ zX+Z^&B9$wf6j?PgIHSZaKEAy=~LXYILay zy^aLQOy^?527Yy-)RB~bzHy5^1rim>A+LC$WJvB_Be%D%#8R`8OVTDzBODnJTk)Ut z(`V)#l5q4guY(nk0X&DJupE<6#PaA6F2RgDysM1b>c6q@w)v})!CF2;QhT*c$&Hj! z#H|JmwzrG5;#d=lPujkO()%#F4>S23%Y-hNy6yiWinKjmPgshVR#(S*G&$52T$ssYb4eD-UoPX^0v~E&?_<>7)o|t>F`h_zxn!h?k+S)( zhFjKu@;SCKGG553pT9nQ-XVGz8%e=5A;`dG0;DtiUHI4&}BR_jt*!CZC!az5AnOd-+BKP6n`6HrATYrk5f-yDoy8_z0G~U zv(#)C+Uv<-BJ0FX!^g)bk@sLM1=I-LC%j6ng>f$Pg8kU~%Iej$8%@+)EbBCok>O#? zSMD`}Qb@iFb$I!3+U?ktv}sMOMhV-0W8B?)fMrPOasH-1#J)>M z6*Y6cDNs@@KTz7C>jg|h?1qhsL*tLu*%Bea00;tlHYE{}&(-0)S~maH@$%fzP>JWCNr|URZ6_x- zrdmRxS#aUPmSKB;+y1PW_aj?B_ZDHZsm@eos$Y~^5 z1DC8USkKvj&kUeQ-8P|s+aZHDFZiB$&qMX(pTqZTtIycz@|LLr=Jw~taY&hm0&~CP zjoT-emBv>U(SRW}A}Xp+|K{py-lw=R5ZR|3g^)}g0T6jPr^Ca;rHHHb{M0aD9(=XK z+<*nyg=A;kpLr2*B^zO~)nj00;H=h#E!SD1@Kbl)A66tKB>^PH7%{xeCEhOq+T`Jh ziMJhR?~~2N!V$2?06!SU47{$F5uj^#%-RBufD(u3^03o?GE;ju9?z`4+f$tWNwj}aOgy7XQefkmej{Hf|g3k)Ea zB_7WZy1rTt#)I;1%mQ0q{z$y^7&`$W#x(G6r2b>O7*Jcs5x#|tShfB+U$-4&tF@o| z4&0$w;O$u|vo_#N5w)Ep*Pcc=CgCjv%&@hUlGGTKqQqR+Mg&ttn_9RYo^36>1nDby zHCx5b{9U_%Cob^>85|m5&J0lErD>DRE~Td7!YMC;njGehjE&7tj%TkVS?T(M)_?eA^qSWZ zY`9CdQ0#Kx9-ZC^mMf3NltFm7xsQ*JRf`s1!`}XOeVS#E8{T+wUTbgKMvoDZOkq$f zC@cg@*&txHvz^Gt6NAVyMjZ*r@L{*37TyY;0MGlmRS{ZB4K7=ZVtUt=6qeNm$C?*- zK2mGmXVMk;$|sssNrr(SGypJEhP*tOs(RLL+VgDI9IbT-*s(cKP*AYymRmVGW@l!W zxpC1bU6`r1QVDq=Y6k$6IDy6>bIZfuouSso=H|f(bVPLP8Mr|1p!>_vPH~|4CVo|+ zw)3ZeQ$s;cZi^p_3YUDkP(Hz7wY9FMhNBAs?ZW2o_CQjPfB2+-$kP=OLr=AFzMu91 z92`R0zdZsSvm$BD!dwii|Li1t1p(1e)zFbQZ?qs3)ri)Rg-Qd@$J@Wr zznUC{U#1{ZVYM*HgoSqolVdEgJxGe*8U3V1{%d3^BW< z9UOJ7snd8YbQ{94{EK{qG^Xu@4%LX=n}Hj81W;z$X*H`>0xtERQFc&FR7FSf*HVVt zP3p^i^(bNA%Rhret0iDfuhIf@@y)>5DCmcC;&J*gIY}RTnAuv+7Ad`}VTjz<_|%*8 z%peWWI_6ONH*b3Sb$=>$zSeJs-!3aF1D0*LF2XNkn*6uss;xWKv3n*Q*CIdNzHA*O z7QIpV0tI1*A+7O({ctY^0O&6*x2Ji>EQmU)nq@UiYzmN59NWK^Dh+rHT>(G7BojyB zjE-BaVKL%9H*(IV$)GTLLyPMMrRhFCqx$BD55shBoN8YMaT^{=@`BN1)Ou_qpuAvv zEOfh!1O4*Z>K@6V8~lL8P3_5!)2=uJD)Be%;|Tbkfq&nUp7CJ}Mu^-apF%!=#wJ|Z zN4UlU2oWrzrb z=Z^P)3tZymkq7el^yJ=`C+x-2McLfk{=yW;wUy`sMT!wwnJrd1UF&E8Z1I*uiGVL0 z^|G@={OaIy|yh=sP&-)Q19@P{CdJJ)XmLqi-N znw@~zB+S}B?55CBR|5Bdm(x#*Bv5}aSHeoDt7t9dOA41o)b&In{E_K|!3MUuX$IF0 zcwJJ%iB)tftFS4ABOc&H%QyGQ0j$f(p5*4nw5tI2f;jVi=kU|32K@tXxHkCdqoM(sA<-&RD#|IpFmW*d(ggeR1O0z>=NNEdAXvytZP1=zy#@{)#aWdi(z d{BMCGz+n*aE8h?6F9Ap3l;qT9tEJ7t{vWcdg(?65 diff --git a/static/images/salagata/complexDocs/scratchAngles.png b/static/images/salagata/complexDocs/scratchAngles.png deleted file mode 100644 index 92e8a98d2dd25b6d5a828766c212a84362ad5eaf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11355 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 Z!Q3cgPyqw@f4|;<$w?_mR*M@2{U1+;y#W9K From 39529602a4503b0b60f9d4e9490487d345cfd475 Mon Sep 17 00:00:00 2001 From: salagata <122649005+salagata@users.noreply.github.com> Date: Sat, 5 Sep 2026 20:16:36 -0500 Subject: [PATCH 8/9] Fixed many errors --- src/lib/Documentation/SalagataComplex.md | 7 ++++--- src/lib/Documentation/pages.js | 2 +- static/extensions/salagata/complex.js | 6 +++--- static/images/salagata/complex_placeholder.svg | 2 +- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/lib/Documentation/SalagataComplex.md b/src/lib/Documentation/SalagataComplex.md index fedc55068..b0e121e25 100644 --- a/src/lib/Documentation/SalagataComplex.md +++ b/src/lib/Documentation/SalagataComplex.md @@ -17,10 +17,11 @@ These Complex numbers have the property to choose the representation you want to While developing this extension, I found a problem in the way that Scratch represents the Angles (Internally, and in ANGLE inputs). ![Representation of angles in Scratch](https://files.catbox.moe/7uz5t7.png) -The angles basically start in 0° but 0° is the top (when it should be in the right), then the front is 90°(when it should be in the top), which it's confusing. -The trigonometrical functions (even the ones defined in ([sin v] of ()) block), recieve as argument a different kind of angle. +In Scratch, 90° is pointing right, with 0° pointing up, which is different from the common representation which is the opposite. +The trigonometrical functions (even the ones defined in ([sin v] of ()) block, Scratch-core), recieve as argument a different kind of angle. Internally, Scratch has to do `90 - SCRATCH_ANGLE` in order to perform any trigonometrical operation. It converts the Scratch angle recieved commonly as input into a Plane angle(The one used in Mathematics). ![Complex Plane angles](https://files.catbox.moe/umf5i8.png) +> ‍Kan8eDie, CC BY-SA 3.0 , via Wikimedia Commons In this documentation. I'm going to define `Scratch Angle` as the angles that are used in Scratch angle inputs, and `Complex Plane angle` as the angles that are used in Trigonometry (and Complex Analysis). And `to transform ANGLE into ANGLE` to the mathematical operation of converting one kind of angle into another, and viceversa. ```scratch set direction to (90) ::motion // Arguments are in Scratch angles @@ -31,7 +32,7 @@ direction ::motion reporter // Returns in Scratch angles In general. ``` COMPLEX_PLANE_ANGLE = 90 - SCRATCH_ANGLE -SCRATCH_ANGLE = -COMPLEX_PLANE_ANGLE + 90 +SCRATCH_ANGLE = 90 - COMPLEX_PLANE_ANGLE ``` JwVector doesn't solve the problem, it just abstracts the vector functions for use only Scratch angles (in addition of casting degrees to radians and floating-point precision errors). diff --git a/src/lib/Documentation/pages.js b/src/lib/Documentation/pages.js index d40d8da95..c7b75882b 100644 --- a/src/lib/Documentation/pages.js +++ b/src/lib/Documentation/pages.js @@ -86,5 +86,5 @@ export default { "DateFormatV2": DateFormatV2, // Complex Numebrs - "SalagataComplex": PageComplexNumbers + "SalagataComplex": PageComplexNumbers, }; diff --git a/static/extensions/salagata/complex.js b/static/extensions/salagata/complex.js index b4327e338..0f2b57aad 100644 --- a/static/extensions/salagata/complex.js +++ b/static/extensions/salagata/complex.js @@ -546,9 +546,9 @@ id: "salagataComplexNumber", name: this.formatMessage("Complex Numbers"), description: this.formatMessage("Complex Number Type for do complex analysis functions, perfect for rotation where vectors are slow"), - color1: "#c3ba5e", - menuIconURI: "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSIxMjEuNjIxNjIiIGhlaWdodD0iMTIxLjYyMTYyIiB2aWV3Qm94PSIwLDAsMTIxLjYyMTYyLDEyMS42MjE2MiI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTE3OS4xODkxOSwtMTE5LjE4OTE5KSI+PGcgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjAiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCI+PHBhdGggZD0iTTE3OS4xODkxOSwxODBjMCwtMzMuNTg0ODggMjcuMjI1OTMsLTYwLjgxMDgxIDYwLjgxMDgxLC02MC44MTA4MWMzMy41ODQ4OCwwIDYwLjgxMDgxLDI3LjIyNTkzIDYwLjgxMDgxLDYwLjgxMDgxYzAsMzMuNTg0ODggLTI3LjIyNTkzLDYwLjgxMDgxIC02MC44MTA4MSw2MC44MTA4MWMtMzMuNTg0ODgsMCAtNjAuODEwODEsLTI3LjIyNTkzIC02MC44MTA4MSwtNjAuODEwODF6IiBmaWxsPSIjYzNiYTVlIiBzdHJva2UtbGluZWNhcD0iYnV0dCIvPjxwYXRoIGQ9Ik0yNjIuMjMyNjksMTQ1LjA0NDA5YzAsNS4yMDA3NyAtNS4wMzI2Myw4LjYwNDQ2IC0xMi4zMTM3MSw4LjYwNDQ2Yy03LjI4MTA3LDAgLTEwLjc4NjUzLC0xLjEzNDE5IC0xMC43ODY1MywtNi4zMzQ5NWMwLC01LjIwMDc3IDQuNTc3NjgsLTkuODU5MjMgMTEuODU4NzUsLTkuODU5MjNjNy4yODEwOCwwIDExLjI0MTUsMi4zODg5NyAxMS4yNDE1LDcuNTg5NzR6IiBmaWxsPSIjZmZlYTAwIiBzdHJva2UtbGluZWNhcD0iYnV0dCIvPjxwYXRoIGQ9Ik0yNTEuNzQ3MDYsMTc3LjQ1Nzk4Yy0xLjY1ODEzLDMuNDYzNTggLTMuNTEyMzEsNi42ODI2OCAtNi45MDI3OCwxNC4zNjA4M2MtMy4wMTc0OCw2LjgzMzQ3IC0xMS45MjA0OCwyMi41MTA4MiAtOS45MDgzLDIzLjUxOTkyYzMuNTYwMzQsNS40MTE3MyA1LjgyMTY2LDQuODExMDMgMTMuNDkyMiw2LjU5Njk1Yy00LjE5Nzc1LDEuOTg4MDggLTIyLjAyNjk5LC0xLjUwMDUzIC0yNC41MTA2OSwtMS43MjgyNWMtMTEuNTg2NDcsLTEuMDYyMzIgLTQuNTg4MiwtMTcuMTg3OTQgMi45MTMxNSwtMzAuODg4MTJjMi4xNTEyLC0zLjkyODg4IDkuMzYyMTgsLTE1LjIyNjM4IDkuMTUzMzMsLTE4LjY4MTI3Yy0wLjQwNzg2LC02Ljc0NzExIC0xNy42OTM1OCwtNC44MzI4OCAtMTUuMzIxMTMsLTYuOTk0NzhjMi41MjM3NiwtMi4yOTk3OSAyMy41MTYyNiwtMC4xNzM0MyAyOC45MzMsMC43MzMxOWM0LjQzOTYyLDEuNDg3NjkgNi4yOTA0MSwxLjk2NjUyIDYuMjI5NTUsNC4xOTcyOGMtMC4wNzUxLDIuNzUyOTQgLTMuMTYzNzMsNi4yMjUxNyAtNC4wNzgzMyw4Ljg4NDI0eiIgZmlsbD0iI2ZmZWEwMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIi8+PC9nPjwvZz48L3N2Zz4=", - docsURI + color1: "#847e3f", + menuIconURI: "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSIxMjEuNjIxNjIiIGhlaWdodD0iMTIxLjYyMTYyIiB2aWV3Qm94PSIwLDAsMTIxLjYyMTYyLDEyMS42MjE2MiI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTE3OS4xODkxOSwtMTE5LjE4OTE5KSI+PGcgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjAiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCI+PHBhdGggZD0iTTE3OS4xODkxOSwxODBjMCwtMzMuNTg0ODggMjcuMjI1OTMsLTYwLjgxMDgxIDYwLjgxMDgxLC02MC44MTA4MWMzMy41ODQ4OCwwIDYwLjgxMDgxLDI3LjIyNTkzIDYwLjgxMDgxLDYwLjgxMDgxYzAsMzMuNTg0ODggLTI3LjIyNTkzLDYwLjgxMDgxIC02MC44MTA4MSw2MC44MTA4MWMtMzMuNTg0ODgsMCAtNjAuODEwODEsLTI3LjIyNTkzIC02MC44MTA4MSwtNjAuODEwODF6IiBmaWxsPSIjODQ3ZTNmIiBzdHJva2UtbGluZWNhcD0iYnV0dCIvPjxwYXRoIGQ9Ik0yNjIuMjMyNjksMTQ1LjA0NDA5YzAsNS4yMDA3NyAtNS4wMzI2Myw4LjYwNDQ2IC0xMi4zMTM3MSw4LjYwNDQ2Yy03LjI4MTA3LDAgLTEwLjc4NjUzLC0xLjEzNDE5IC0xMC43ODY1MywtNi4zMzQ5NWMwLC01LjIwMDc3IDQuNTc3NjgsLTkuODU5MjMgMTEuODU4NzUsLTkuODU5MjNjNy4yODEwOCwwIDExLjI0MTUsMi4zODg5NyAxMS4yNDE1LDcuNTg5NzR6IiBmaWxsPSIjZmZlYTAwIiBzdHJva2UtbGluZWNhcD0iYnV0dCIvPjxwYXRoIGQ9Ik0yNTEuNzQ3MDYsMTc3LjQ1Nzk4Yy0xLjY1ODEzLDMuNDYzNTggLTMuNTEyMzEsNi42ODI2OCAtNi45MDI3OCwxNC4zNjA4M2MtMy4wMTc0OCw2LjgzMzQ3IC0xMS45MjA0OCwyMi41MTA4MiAtOS45MDgzLDIzLjUxOTkyYzMuNTYwMzQsNS40MTE3MyA1LjgyMTY2LDQuODExMDMgMTMuNDkyMiw2LjU5Njk1Yy00LjE5Nzc1LDEuOTg4MDggLTIyLjAyNjk5LC0xLjUwMDUzIC0yNC41MTA2OSwtMS43MjgyNWMtMTEuNTg2NDcsLTEuMDYyMzIgLTQuNTg4MiwtMTcuMTg3OTQgMi45MTMxNSwtMzAuODg4MTJjMi4xNTEyLC0zLjkyODg4IDkuMzYyMTgsLTE1LjIyNjM4IDkuMTUzMzMsLTE4LjY4MTI3Yy0wLjQwNzg2LC02Ljc0NzExIC0xNy42OTM1OCwtNC44MzI4OCAtMTUuMzIxMTMsLTYuOTk0NzhjMi41MjM3NiwtMi4yOTk3OSAyMy41MTYyNiwtMC4xNzM0MyAyOC45MzMsMC43MzMxOWM0LjQzOTYyLDEuNDg3NjkgNi4yOTA0MSwxLjk2NjUyIDYuMjI5NTUsNC4xOTcyOGMtMC4wNzUxLDIuNzUyOTQgLTMuMTYzNzMsNi4yMjUxNyAtNC4wNzgzMyw4Ljg4NDI0eiIgZmlsbD0iI2ZmZWEwMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIi8+PC9nPjwvZz48L3N2Zz4=", + docsURI: "https://extensions.penguinmod.com/docs/SalagataComplex", // blockText: "#000000", blocks: [ { diff --git a/static/images/salagata/complex_placeholder.svg b/static/images/salagata/complex_placeholder.svg index 8122f4b78..12b010e44 100644 --- a/static/images/salagata/complex_placeholder.svg +++ b/static/images/salagata/complex_placeholder.svg @@ -1 +1 @@ -e = cos(π) + i sin(π) z - a = 0nax + bx + c = 02θr1∠ɸz0z1z2z3z4z5z6ɸ-1(5+√15 i)+(5-√15 i) = 10(5+√15 i)x(5-√15 i) = 40a+b = 10axb = 40x + 10x - 40 = 02a + bir∠θ \ No newline at end of file +e = cos(π) + i sin(π) z - a = 0nax + bx + c = 02θr1∠ɸz0z1z2z3z4z5z6ɸ-1(5+√15 i)+(5-√15 i) = 10(5+√15 i)x(5-√15 i) = 40a+b = 10axb = 40x + 10x - 40 = 02a + bir∠θ \ No newline at end of file From 8fe330f10e76ea1778894d924af2a0291104d70f Mon Sep 17 00:00:00 2001 From: salagata <122649005+salagata@users.noreply.github.com> Date: Sun, 6 Sep 2026 01:57:42 -0500 Subject: [PATCH 9/9] Updated the docs --- src/lib/Documentation/SalagataComplex.md | 231 ++++++++++++- static/extensions/salagata/complex.js | 412 ++++++++++++++++++----- 2 files changed, 541 insertions(+), 102 deletions(-) diff --git a/src/lib/Documentation/SalagataComplex.md b/src/lib/Documentation/SalagataComplex.md index b0e121e25..803160885 100644 --- a/src/lib/Documentation/SalagataComplex.md +++ b/src/lib/Documentation/SalagataComplex.md @@ -2,7 +2,7 @@ --- -[Complex Numbers](https://en.wikipedia.org/wiki/Complex_number) is an extension that allows you to use Complex Numbers in Scratch, by introducing a new type: `ComplexNumber`. +[Complex Numbers](https://en.wikipedia.org/wiki/Complex_number) is an extension that allows you to use Complex Numbers in Scratch, by introducing a new type: `ComplexNumber`.
These Complex Numbers can be used in, for example: - Computer Graphics: Used for do transforms, simulations like the design of fractals(like the Mandelbrot Set) and rendering images. - Video games and 2D/3D graphics: Rotations, Scaling, Translation, Reflection. @@ -15,13 +15,13 @@ These Complex numbers have the property to choose the representation you want to ## Angles -While developing this extension, I found a problem in the way that Scratch represents the Angles (Internally, and in ANGLE inputs). -![Representation of angles in Scratch](https://files.catbox.moe/7uz5t7.png) +While developing this extension, I found a problem in the way that Scratch represents the Angles (Internally, and in ANGLE inputs). +![Representation of angles in Scratch](https://files.catbox.moe/7uz5t7.png)
In Scratch, 90° is pointing right, with 0° pointing up, which is different from the common representation which is the opposite. -The trigonometrical functions (even the ones defined in ([sin v] of ()) block, Scratch-core), recieve as argument a different kind of angle. +The trigonometrical functions (even the ones defined in ([sin v] of ()) block, Scratch-core), recieve as argument a different kind of angle.
Internally, Scratch has to do `90 - SCRATCH_ANGLE` in order to perform any trigonometrical operation. It converts the Scratch angle recieved commonly as input into a Plane angle(The one used in Mathematics). ![Complex Plane angles](https://files.catbox.moe/umf5i8.png) -> ‍Kan8eDie, CC BY-SA 3.0 , via Wikimedia Commons +> ‍Kan8eDie, CC BY-SA 3.0 , via Wikimedia Commons
In this documentation. I'm going to define `Scratch Angle` as the angles that are used in Scratch angle inputs, and `Complex Plane angle` as the angles that are used in Trigonometry (and Complex Analysis). And `to transform ANGLE into ANGLE` to the mathematical operation of converting one kind of angle into another, and viceversa. ```scratch set direction to (90) ::motion // Arguments are in Scratch angles @@ -56,8 +56,9 @@ transform (0) into Scratch Angle ::operators reporter // Trasnsforms Complex Pl This might look confusing(because it is), so, stay with the idea that Scratch uses a different kind of angles that doesn't follow the same properties as the normal angles. And you just need to "rotate and invert them" to convert one into another ## Internal Representation -The `ComplexNumberType` in this extension has 2 representations. Rectangular and Polar. -At the moment of creating a complex number in polar form, it is created +The `ComplexNumberType` in this extension has 2 representations. Rectangular and Polar.
+This division is for be specific if you want to save the phase and modulus, and to not recalculate them each time. +At the moment of creating a complex number in polar form, instead of calculating the real and imaginary part and lose the phase and modulus(which will lead to precision error), it saves the phase and modulus in packages properties, and then, it calculates the real and imaginary part, while using the phase and modulus for calculus that involves the polar components (maybe will change this to choose if do the math using `rectangular or polar` or either `rectangular` or `polar`, for improve optimization). ### Rectangular representation @@ -68,11 +69,223 @@ At the moment of creating a complex number in polar form, it is created ### Polar representation +| Property | Description | +| ------------- |:-------------:| +| real | Real part | +| imaginary | Imaginary Part | +| _modulus | *(Package property)* Modulus of the angle | +| _phase | *(Package property)* The complex phase(in degrees) transformed into a *complex plane angle* | +| _fromPolar | *(Package property)* If the complex number is or comes from a `Polar->Complex` function, like `new complex number modulus: [1], angle: [45]` | + +At the moment of needing the phase or the modulus, if it comes from the polar form, it will use `_phase` or `_modulus`, if no, then it will calculate it. + +## Parsing, Serialization and Deserialization + +For parsing functions like .toArray(), toJSON(), it will return a polar representation if `_fromPolar` is `true`, if not, it will return a rectangular representation, as shown as above. + +At the moment of returning an Array, a delimitated string, or any well-sorted format, it will use the next order. Whether to use Pthe Rectangular or Polar representation is determined by the `_fromPolar` property + +### Rectangular representation + +Representation used at the moment of parsing Arrays, or any well-sorted format into Complex Numbers +| Index | Description | +| ------------- |:-------------:| +| 0 | Real part | +| 1 | Imaginary Part | + +Representation used at the moment of parsing Objects into Complex Numbers (NOT FINISHED YET) +| Property | Description | +| ------------- |:-------------:| +| real | Real part | +| imaginary | Imaginary Part | + +--- + +### Polar representation + +Representation used at the moment of parsing Arrays, or any well-sorted format into Complex Numbers +| Index | Description | +| ------------- |:-------------:| +| 0 | Real part | +| 1 | Imaginary Part | +| 2 | Modulus of the angle | +| 3 | The complex phase(in degrees) as a *scratch angle* | + +Representation used at the moment of casting Objects into Complex Numbers (NOT FINISHED YET) | Property | Description | | ------------- |:-------------:| | real | Real part | | imaginary | Imaginary Part | | modulus | Modulus of the angle | -| phase | The complex phase transfomed into a *complex plane angle* | +| phase | The complex phase(in degrees) as a *scratch angle* | + +Serialization and Deserialization are done using an array, using the indexes as shown above.
+If the index number 3 exist `z[3]`, at the moment of serialization it will *transform it into a scratch angle*, and at the moment of deserialization, it will *transform it back as a complex plane angle*.
+This distinction is important for extensions with functions that use a `.toJSON()` function or similar, and uses only the JSON representation(Like in SwiftJSON, where the complex numbers when they are in a list are shown as JSON objects, but at the moment of accessing it's properties, it's the `ComplexNumberType`), because the angle internally is represented as a *complex plane angle*, but it returns a *scratch angle* for most of the blocks. This extension has already an integration with JwArray and dogeiscutSet, although you can put the blocks that return a list inside functions like in the next example with SwiftJSON + +```scratch +for each (key) (value) in [{"Object": "or array"}] { + do something with (value) :: extension +} :: #748BEE // SwiftJSON inputs are the raw objects, no the "parsed" objects +// they mantain the same properties and prototypes, you should check if your extension allows this +``` + +## Extension Object +This extension saves many data related to the extension inside the object `vm.salagataComplexNumber`, which has the following properties. + +| Property | Definition | Description | +| ------------- | ------------- |:-------------:| +| `Type` | `typeof ComplexNumberType` | Custom Type class defined for Complex Numbers | +| `Block` | `object` | Template for generate blocks compatible with the Custom Type | +| `Argument` | `object` | Template for generate arguments compatible with the Custom Type | +| `Serializer` | `function(z: ComplexNumberType): [number,number] \| [number,number,number,number]` | Serializer for the Custom Type | +| `Deseralizer` | `function(z: [number,number] \| [number,number,number,number]): ComplexNumberType` | Deserializer for the Custom Type | + + +--- + +The type Complex Number Type has the following properties and methods, in addition of the mandatory ones for Custom Types. + +| Property | Definition | Description | +| ------------- | ------------- |:-------------:| +| `customId` | `String` | Used for identify the object during serialization | +| `toReporterContent` | `function(): HTMLElement` | Content for a script reporter | +| `toMonitorContent` | `function():HTMLElement` | Content for a variable monitor | +| `toString` | `function(polarForm = false): String` | Casts the complex number as a string, choose whether to use rectangular or polar form. Choose whether to convert it as polar form when casting to string | + +The table shown in Internal Representation Section, all values are stored as numbers + +| Property | Definition | Description | +| ------------- | ------------- |:-------------:| +| `toComplex` | `static function(u): ComplexNumberType` | Internal parsing functions, representations are shown in Parsing, Serialization and Deserialization Section. It has support for arrays[2,4], strings delimited by commas[2,4], VectorType from JwVector, ArrayType from JwArray[2,4], and numbers. If failed, it fallbacks to 0 | +| `jwArrayHandler` | `function(): string` | Representation in a reporter content from `ArrayType` defined in JwArray | +| `modulus` | `function(): number` | Returns the absolute value or modulus of a complex number, if it comes from the polar form, it will use `_modulus`, if no, then it will calculate it. | +| `phase` | `function(): number` | Returns the argument or phase of a complex number in degrees, if it comes from the polar form, it will use `_phase`, if no, then it will calculate it. | +| `conjugate` | `function(): ComplexNumberType` | Returns the conjugate of a complex number, if it comes from the polar form, it will also count the angle and process it, instead of relying that *"it will be recovered after calculating it back after calculating `real` and `imaginary`"* | +| `toJSON` | `function(): { real: number; imaginary: number; modulus?: number \| null; phase?: number; }` | Representation as a JSON Object, (seems like it's only used in SwiftJSON and in dogeiscutObject). Remember that the phase is in degrees and *transformed into a scratch angle* +| `toArray` | `function(): [number,number] \| [number,number,number,number]` | Representation as an Array. Remember that the phase is in degrees and *transformed into a scratch angle* +| `fromPolar` | `function(modulus: number, phase: number): ComplexNumberType` | Creates a complex number given it's polar form. `modulus` The absolute value, or modulus of the original number, `phase` The angle, in degrees, transformed as a *complex plane angle* + + +NOTE: **IS NOT RECOMMENDED TO RETRIEVE THE VALUE DIRECLY USING THE PARSED OBJECT OR ARRAY REPRESENTATION, as it doesn't have the phase correcly expressed**, it's recommended to use the blocks already defined in the extension, or use the getters `modulus` and `phase`. + +```scratch +(get [real] in (parse [A complex number] as an object :: #EEB354) :: #EEB354) // try avoiding this +``` + +# Reference + +NOTE: The bumped inputs cannot be displayed here, and we have turned them into circular inputs. + +--- +```scratch +(complex number from [REAL] :: #847E3F) +``` +Creates a new complex number in rectangular form with only the real part, imaginary part as 0 + +| Argument | Description | +| ------------- |:-------------:| +| REAL | Real part | + +--- +```scratch +(complex number from [IMAGINARY] i :: #847E3F) +``` +Creates a new complex number in rectangular form with only the imaginary part, real part as 0 + +| Argument | Description | +| ------------- |:-------------:| +| IMAGINARY | Imaginary part | + +--- +```scratch +(complex number [REAL] + [IMAGINARY] i :: #847E3F) +``` +Creates a new complex number in rectangular form with the real part and imaginary part +| Argument | Description | +| ------------- |:-------------:| +| REAL | Real part | +| IMAGINARY | Imaginary part | + +--- +```scratch +(complex number modulus: [R] phase: [PHASE] :: #847E3F) +``` +Creates a new complex number in polar form with modulus and phase as a *scratch angle* +| Argument | Description | +| ------------- |:-------------:| +| R | Modulus of complex number| +| PHASE | Phase of complex number, in degrees, as a *scratch angle* | + + +--- +```scratch +(complex number modulus: 1 phase: [PHASE] :: #847E3F) +``` +Creates a new complex number in polar form with modulus 1 and phase as a *scratch angle* +| Argument | Description | +| ------------- |:-------------:| +| PHASE | Phase of complex number, in degrees, as a *scratch angle* | + +--- +```scratch +(parse [A] to a complex number :: #847E3F) +``` +Parses a type into a complex number +| Argument | Description | +| ------------- |:-------------:| +| A | A compatible type. At the moment it has support for arrays[2,4], strings delimited by commas[2,4], VectorType from JwVector, ArrayType from JwArray[2,4], and numbers. If failed, it fallbacks to 0 | +Check Parsing, Serialization and Deserializaiton + +--- +```scratch +(real part [A] :: #847E3F) +``` +Returns the Real part of a complex number, independent of it's representation +| Argument | Description | +| ------------- |:-------------:| +| A | Complex number using any representation | + +--- +```scratch +(imaginary part [A] :: #847E3F) +``` +Returns the Imaginary part of a complex number, independent of it's representation +| Argument | Description | +| ------------- |:-------------:| +| A | Complex number using any representation | + +--- +```scratch +(absolute value [A] :: #847E3F) +``` +Returns the Absolute value, or modulus of a complex number, independent of it's representation +| Argument | Description | +| ------------- |:-------------:| +| A | Complex number using any representation | + +--- +```scratch +(phase [A] :: #847E3F) +``` +Returns the Argument, or the phase of a complex number *as a scratch angle*, independent of it's representation +| Argument | Description | +| ------------- |:-------------:| +| A | Complex number using any representation | + -NOTE: The bumped inputs cannot be displayed here, and we have turned them into circular inputs. \ No newline at end of file +UNFINISHED \ No newline at end of file diff --git a/static/extensions/salagata/complex.js b/static/extensions/salagata/complex.js index 0f2b57aad..af8ef8d5b 100644 --- a/static/extensions/salagata/complex.js +++ b/static/extensions/salagata/complex.js @@ -17,61 +17,67 @@ // function parseStringToComplex(str) { // } - Scratch.translate.setup({ - es: { - "Complex Numbers": "Números Complejos", - "Complex Number Type for do complex analysis functions, perfect for rotation where vectors are slow": - "El tipo de dato de Números complejos para realizar funciones de analisis complejo, perfecto para rotaciones donde los vectores son lentos", - "complex number from [REAL]": "número complejo desde [REAL]", - "complex number from [IMAGINARY]i": "número complejo desde [IMAGINARY]i", - "complex number [REAL] + [IMAGINARY]i": "número complejo [REAL] + [IMAGINARY]i", - "complex number modulus: [R] phase: [PHASE]": "número complejo de módulo: [R] fase: [PHASE]", - "complex number modulus: 1 phase: [PHASE]": "número complejo de módulo: 1 fase [PHASE]", - "real part [A]": "parte real [A]", - "imaginary part [A]": "parte imaginaria [A]", - "absolute value [A]": "valor absoluto [A]", - "phase [A]": "fase [A]", - "conjugate [A]": "conjugado [A]", - "[A] x [B] using [FORM]": "[A] x [B] usando [FORM]", - "[A] / [B] using [FORM]": "[A] / [B] usando [FORM]", - "[A] ^ [B] using [FORM]": "[A] ^ [B] usando [FORM]", - "multiply [A] with its conjugate":"multiplicar [A] con su conjugado", - "reciprocal [A]": "recíproca [A]", - "parse [A] to a complex number": "convertir [A] a un número complejo", - // "complex number in polar form modulus: [R] phase: [PHASE]": "número complejo en forma polar modulo: [R] fase [PHASE]", - "[COMPLEX] to [FORM] as text": "[COMPLEX] a [FORM] como texto", - "use [FORM] for [COMPLEX]": "usar [FORM] para [COMPLEX]", - // "multiply [A] with [B] using the polar form": "multiplicar [A] con [B] usando la forma polar", - // "divide [A] with [B] using the polar form": "dividir [A] con [B] usando la forma polar", - "[A] ^ [B] using the polar form": "[A] ^ [B] usando la forma polar", - "square root of [A]": "raíz cuadrada de [A]", - "[B]th root of [A] using the polar form": "[B]ésima raíz de [A] usando la forma polar", - "solutions of equation [A]x^2 + [B]x + [C] = 0": "soluciones de la ecuación [A]x^2 + [B]x + [C] = 0", - "[SOLUTION] solution of equation [A]x^2 + [B]x + [C] = 0": "[SOLUTION] solución de la ecuación [A]x^2 + [B]x + [C] = 0", - "positive": "positiva", - "negative": "negativa", - "first": "primera", - "second": "segunda", - "polar form": "forma polar", - "rectangular form": "forma rectangular", - // "roots of equation x^[A] - [B] = 0": "raices de la ecuación x^[A] - [B] = 0", - "roots of equation [C]x^[A] - [B] = 0": "raices de la ecuación [C]x^[A] - [B] = 0", - // "[D]th root of equation x^[A] - [B] = 0": "[D]ava raíz de la ecuación x^[A] - [B] = 0", - "[D]th root of equation [C]x^[A] - [B] = 0": "[D]ava raíz de la ecuación [C]x^[A] - [B] = 0", - 'position in [FORM]': "posición en [FORM]", - 'go to [COMPLEX] using [FORM]': "ir a [COMPLEX] usando [FORM]", - 'direction in [FORM]': "dirección en [FORM]", - 'point in sense of [COMPLEX] using [FORM]': "apuntar en sentido de [COMPLEX] usando [FORM]", - 'stretch in [FORM]': "estiramiento en [FORM]", - 'set stretch to [COMPLEX] using [FORM]': "establecer estiramiento en [COMPLEX] usando [FORM]", - 'mouse position in [FORM]': "posición del ratión en [FORM]", - "convert [COMPLEX] to vector": "convertir [COMPLEX] a vector", - "convert [VECTOR] to complex number": "convertir [VECTOR] a número complejo", - "transform [ANGLE] into Complex Plane Angle": "transformar [ANGLE] en ángulo del plano complejo", - "transform [ANGLE] into Scratch Angle": "transformar [ANGLE] en ángulo de Scratch", - }, - }); - + Scratch.translate.setup({ + es: { + "Complex Numbers": "Números Complejos", + "Complex Number Type for do complex analysis functions, perfect for rotation where vectors are slow": + "El tipo de dato de Números complejos para realizar funciones de analisis complejo, perfecto para rotaciones donde los vectores son lentos", + "complex number from [REAL]": "número complejo desde [REAL]", + "complex number from [IMAGINARY]i": "número complejo desde [IMAGINARY]i", + "complex number [REAL] + [IMAGINARY]i": "número complejo [REAL] + [IMAGINARY]i", + "complex number modulus: [R] phase: [PHASE]": "número complejo de módulo: [R] fase: [PHASE]", + "complex number modulus: 1 phase: [PHASE]": "número complejo de módulo: 1 fase [PHASE]", + "real part [A]": "parte real [A]", + "imaginary part [A]": "parte imaginaria [A]", + "absolute value [A]": "valor absoluto [A]", + "phase [A]": "fase [A]", + "conjugate [A]": "conjugado [A]", + "[A] x [B] using [FORM]": "[A] x [B] usando [FORM]", + "[A] / [B] using [FORM]": "[A] / [B] usando [FORM]", + "[A] ^ [B] using [FORM]": "[A] ^ [B] usando [FORM]", + "multiply [A] with its conjugate":"multiplicar [A] con su conjugado", + "reciprocal [A]": "recíproca [A]", + "parse [A] to a complex number": "convertir [A] a un número complejo", + // "complex number in polar form modulus: [R] phase: [PHASE]": "número complejo en forma polar modulo: [R] fase [PHASE]", + "[COMPLEX] to [FORM] as text": "[COMPLEX] a [FORM] como texto", + "use [FORM] for [COMPLEX]": "usar [FORM] para [COMPLEX]", + // "multiply [A] with [B] using the polar form": "multiplicar [A] con [B] usando la forma polar", + // "divide [A] with [B] using the polar form": "dividir [A] con [B] usando la forma polar", + "[A] ^ [B] using the polar form": "[A] ^ [B] usando la forma polar", + "square root of [A]": "raíz cuadrada de [A]", + "[B]th root of [A] using the polar form": "[B]ésima raíz de [A] usando la forma polar", + "solutions of equation [A]x^2 + [B]x + [C] = 0": "soluciones de la ecuación [A]x^2 + [B]x + [C] = 0", + "[SOLUTION] solution of equation [A]x^2 + [B]x + [C] = 0": "[SOLUTION] solución de la ecuación [A]x^2 + [B]x + [C] = 0", + "positive": "positiva", + "negative": "negativa", + "first": "primera", + "second": "segunda", + "polar form": "forma polar", + "rectangular form": "forma rectangular", + // "roots of equation x^[A] - [B] = 0": "raices de la ecuación x^[A] - [B] = 0", + "roots of equation [C]x^[A] - [B] = 0": "raices de la ecuación [C]x^[A] - [B] = 0", + // "[D]th root of equation x^[A] - [B] = 0": "[D]ava raíz de la ecuación x^[A] - [B] = 0", + "[D]th root of equation [C]x^[A] - [B] = 0": "[D]ava raíz de la ecuación [C]x^[A] - [B] = 0", + 'position in [FORM]': "posición en [FORM]", + 'go to [COMPLEX] using [FORM]': "ir a [COMPLEX] usando [FORM]", + 'direction in [FORM]': "dirección en [FORM]", + 'point in sense of [COMPLEX] using [FORM]': "apuntar en sentido de [COMPLEX] usando [FORM]", + 'stretch in [FORM]': "estiramiento en [FORM]", + 'set stretch to [COMPLEX] using [FORM]': "establecer estiramiento en [COMPLEX] usando [FORM]", + 'mouse position in [FORM]': "posición del ratión en [FORM]", + "convert [COMPLEX] to vector": "convertir [COMPLEX] a vector", + "convert [VECTOR] to complex number": "convertir [VECTOR] a número complejo", + "transform [ANGLE] into Complex Plane Angle": "transformar [ANGLE] en ángulo del plano complejo", + "transform [ANGLE] into Scratch Angle": "transformar [ANGLE] en ángulo de Scratch", + }, + }); + + const integrationsEnabled = { + jwVector: false, + jwArray: false, + dogeiscutSet: false, + } + function radianToDegrees(radian) { return radian * (180 / Math.PI); } @@ -133,6 +139,19 @@ return b; } + function constrainAnglePositive(angle) { + const a = angle % 360 + const s = Math.sign(a); + + return (s === -1) ? a + 360 : a; + } + + function constrainAngleClampedPositive(angle) { + const s = Math.sign(angle); + + return (s === -1) ? angle + 360 : angle; + } + function clampAngleRadians(angle) { let a = angle; @@ -269,12 +288,12 @@ */ constructor(real = 0,imaginary = 0, modulus, phase) { // console.log(modulus, angle) - if(!(typeof modulus == "undefined" || typeof phase == "undefined")) { + if(Boolean(modulus) || Boolean(phase)) { this._fromPolar = true; this._modulus = modulus; this._phase = clampAngleDegrees(phase); - switch (this._phase) { + switch (constrainAngleClampedPositive(this._phase)) { case 360: case 0: this.real = modulus; @@ -298,8 +317,8 @@ default: - this.real = (isNaN(real) | real == 0) ? modulus * Degrees.cos(phase) : real; - this.imaginary = (isNaN(imaginary) | imaginary == 0) ? modulus * Degrees.sin(phase) : imaginary; + this.real = (isNaN(real) || real == 0) ? modulus * Degrees.cos(phase) : real; + this.imaginary = (isNaN(imaginary) || imaginary == 0) ? modulus * Degrees.sin(phase) : imaginary; break; } } else { @@ -371,7 +390,11 @@ * @returns {string} */ jwArrayHandler() { - return 'Complex'; + if(this._fromPolar) { + return `Complex<${this.real},${this.imaginary},${this.modulus},${this.phase}>`; + } else { + return `Complex<${this.real},${this.imaginary}>`; + } } /** @@ -409,7 +432,7 @@ } /** - * Returns the argument or phase of a complex number in radians + * Returns the argument or phase of a complex number in degrees * @returns {number} */ get phase() { @@ -460,7 +483,7 @@ * * @static * @param {number} modulus The absolute value, or modulus of the original number - * @param {number} phase The angle, in degrees + * @param {number} phase The angle, in degrees, as a complex plane angle * @returns {ComplexNumberType} */ static fromPolar(modulus, phase) { @@ -513,6 +536,19 @@ } } + // Scratch.vm.runtime.on("EXTENSION_ADDED", () => { + // if(Scratch.vm.runtime.ext_jwArray) { + // integrationsEnabled.jwArray = true; + // } + // if(Scratch.vm.runtime.ext_jwVector) { + // integrationsEnabled.jwVector = true; + // } + // if(Scratch.vm.runtime.ext_dogeiscutSet) { + // integrationsEnabled.dogeiscutSet = true; + // } + // Scratch.vm.runtime.extensionManager.refreshBlocks(); + // }); + class ComplexNumberExtension { constructor() { Scratch.vm.salagataComplexNumber = ComplexNumber, @@ -679,6 +715,38 @@ }, ...ComplexNumber.Block }, + { + opcode: "multiplyScalar", + text: this.formatMessage("[A] x [B] using [FORM]"), + arguments: { + A: ComplexNumber.Argument, + B: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 2 + }, + FORM: { + type: Scratch.ArgumentType.STRING, + menu: 'FORMS' + } + }, + ...ComplexNumber.Block + }, + { + opcode: "divideScalar", + text: this.formatMessage("[A] / [B] using [FORM]"), + arguments: { + A: ComplexNumber.Argument, + B: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 2 + }, + FORM: { + type: Scratch.ArgumentType.STRING, + menu: 'FORMS' + } + }, + ...ComplexNumber.Block + }, { opcode: "multiply", text: this.formatMessage("[A] x [B] using [FORM]"), @@ -931,11 +999,11 @@ arguments: { A: { type: Scratch.ArgumentType.NUMBER, - defaultValue: 1 + defaultValue: 4 }, B: { type: Scratch.ArgumentType.NUMBER, - defaultValue: 0 + defaultValue: 1 }, C: { type: Scratch.ArgumentType.NUMBER, @@ -953,11 +1021,11 @@ arguments: { A: { type: Scratch.ArgumentType.NUMBER, - defaultValue: 1 + defaultValue: 4 }, B: { type: Scratch.ArgumentType.NUMBER, - defaultValue: 0 + defaultValue: 1 }, C: { type: Scratch.ArgumentType.NUMBER, @@ -1015,7 +1083,7 @@ }, { opcode: 'pointTowards', - text: this.formatMessage('point towards [COMPLEX] using [FORM]'), + text: this.formatMessage('point in sense of [COMPLEX] using [FORM]'), arguments: { COMPLEX: ComplexNumber.Argument, FORM: { @@ -1109,6 +1177,7 @@ arguments: { COMPLEX: ComplexNumber.Argument, }, + color1: "#6babff", blockType: Scratch.BlockType.REPORTER, blockShape: Scratch.BlockShape.LEAF, disableMonitor: true, @@ -1129,33 +1198,107 @@ ...ComplexNumber.Block }, - - // ...(Scratch.vm.runtime.ext_jwArray ? ["---"] : []), - // { - // opcode: "roots3", - // text: this.formatMessage("roots of equation [C]x^[A] - [B] = 0"), - // arguments: { - // A: { - // type: Scratch.ArgumentType.NUMBER, - // defaultValue: 1 - // }, - // B: { - // type: Scratch.ArgumentType.NUMBER, - // defaultValue: 0 - // }, - // C: { - // type: Scratch.ArgumentType.NUMBER, - // defaultValue: 1 - // }, - // }, + ...(Scratch.vm.runtime.ext_jwArray ? ["---"] : []), + { + opcode: "quadraticEquationJwArray", + text: this.formatMessage("solutions of equation [A]x^2 + [B]x + [C] = 0"), + arguments: { + A: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1 + }, + B: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 0 + }, + C: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1 + }, + }, - // blockType: Scratch.BlockType.REPORTER, - // blockShape: Scratch.BlockShape.SQUARE, - // disableMonitor: true, - // hideFromPalette: !Scratch.vm.runtime.ext_jwArray, - // ...(Scratch.vm.jwArray ? Scratch.vm.jwArray.Block : {}) - // }, + color1: "#ff513d", + blockType: Scratch.BlockType.REPORTER, + blockShape: Scratch.BlockShape.SQUARE, + disableMonitor: true, + hideFromPalette: !Scratch.vm.runtime.ext_jwArray, + ...(Scratch.vm.jwArray ? Scratch.vm.jwArray.Block : {}) + }, + { + opcode: "rootsJwArray", + text: this.formatMessage("roots of equation [C]x^[A] - [B] = 0"), + arguments: { + A: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 4 + }, + B: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1 + }, + C: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1 + }, + }, + color1: "#ff513d", + blockType: Scratch.BlockType.REPORTER, + blockShape: Scratch.BlockShape.SQUARE, + disableMonitor: true, + hideFromPalette: !Scratch.vm.runtime.ext_jwArray, + ...(Scratch.vm.jwArray ? Scratch.vm.jwArray.Block : {}) + }, + ...(Scratch.vm.runtime.ext_dogeiscutSet ? ["---"] : []), + { + opcode: "quadraticEquationDogeiscutSet", + text: this.formatMessage("solutions of equation [A]x^2 + [B]x + [C] = 0"), + arguments: { + A: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1 + }, + B: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 0 + }, + C: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1 + }, + }, + + color1: "#1ABC9C", + blockType: Scratch.BlockType.REPORTER, + blockShape: Scratch.BlockShape.SQUARE, + disableMonitor: true, + hideFromPalette: !Scratch.vm.runtime.ext_dogeiscutSet, + ...(Scratch.vm.dogeiscutSet ? Scratch.vm.dogeiscutSet.Block : {}) + }, + { + opcode: "rootsDogeiscutSet", + text: this.formatMessage("roots of equation [C]x^[A] - [B] = 0"), + arguments: { + A: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 4 + }, + B: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1 + }, + C: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1 + }, + }, + color1: "#1ABC9C", + blockType: Scratch.BlockType.REPORTER, + blockShape: Scratch.BlockShape.SQUARE, + disableMonitor: true, + hideFromPalette: !Scratch.vm.runtime.ext_dogeiscutSet, + ...(Scratch.vm.dogeiscutSet ? Scratch.vm.dogeiscutSet.Block : {}) + }, ], menus: { SOLUTIONS: { @@ -1295,6 +1438,58 @@ return new ComplexNumberType(A.real - B.real, A.imaginary - B.imaginary); } + multiplyScalar(args) { + const A = ComplexNumberType.toComplex(args.A); + const B = Scratch.Cast.toNumber(args.B); + const FORM = Scratch.Cast.toString(args.FORM); + + switch (FORM) { + case "polar": + return new ComplexNumberType( + A.real * B, + A.imaginary * B + , A.modulus * B + , A.phase + ); + + case "rectangular": + return new ComplexNumberType( + A.real * B, + A.imaginary * B + ); + } + + + + } + + divideScalar(args) { + const A = ComplexNumberType.toComplex(args.A); + const B = Scratch.Cast.toNumber(args.B); + const FORM = Scratch.Cast.toString(args.FORM); + + if(B == 0) { + return NaN; + } + + switch (FORM) { + case "polar": + return new ComplexNumberType( + A.real / B , + A.imaginary / B + , A.modulus / B + , A.phase + ); + case "rectangular": + + return new ComplexNumberType( + A.real / B , + A.imaginary / B + ); + + } + } + multiply(args) { const A = ComplexNumberType.toComplex(args.A); const B = ComplexNumberType.toComplex(args.B); @@ -1794,7 +1989,7 @@ for (let k = 0; k < a; k++) { const phi = (0 + k * 360) / a; - + // console.log(r,phi) roots.push(new ComplexNumberType( 0, // r * Degrees.cos(phi), 0, // r * Degrees.sin(phi), @@ -1937,6 +2132,37 @@ return untransformAngle(angle); } + rootsJwArray(args) { + const a = Math.round(Math.abs(Scratch.Cast.toNumber(args.A))); + const b = Scratch.Cast.toNumber(args.B); + const c = Scratch.Cast.toNumber(args.C); + + return Scratch.vm.jwArray.Type.toArray(this._poly(a,b,c),true); + } + + quadraticEquationJwArray(args) { + const a = Math.round(Scratch.Cast.toNumber(args.A)); + const b = Math.round(Scratch.Cast.toNumber(args.B)); + const c = Math.round(Scratch.Cast.toNumber(args.C)); + + return Scratch.vm.jwArray.Type.toArray(this._quadratic(a,b,c),true); + } + + rootsDogeiscutSet(args) { + const a = Math.round(Math.abs(Scratch.Cast.toNumber(args.A))); + const b = Scratch.Cast.toNumber(args.B); + const c = Scratch.Cast.toNumber(args.C); + + return Scratch.vm.dogeiscutSet.Type.toSet(this._poly(a,b,c)); + } + + quadraticEquationDogeiscutSet(args) { + const a = Math.round(Scratch.Cast.toNumber(args.A)); + const b = Math.round(Scratch.Cast.toNumber(args.B)); + const c = Math.round(Scratch.Cast.toNumber(args.C)); + + return Scratch.vm.dogeiscutSet.Type.toSet(this._quadratic(a,b,c)); + } } Scratch.extensions.register( new ComplexNumberExtension() )