diff --git a/src/lib/Documentation/SalagataComplex.md b/src/lib/Documentation/SalagataComplex.md new file mode 100644 index 000000000..803160885 --- /dev/null +++ b/src/lib/Documentation/SalagataComplex.md @@ -0,0 +1,291 @@ +# 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)
+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 +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 = 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). + +```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.
+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 + +| Property | Description | +| ------------- |:-------------:| +| real | Real part | +| imaginary | Imaginary Part | + +### 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(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 | + + +UNFINISHED \ No newline at end of file diff --git a/src/lib/Documentation/pages.js b/src/lib/Documentation/pages.js index c9c4e4834..a0e4f749d 100644 --- a/src/lib/Documentation/pages.js +++ b/src/lib/Documentation/pages.js @@ -42,6 +42,7 @@ import ProjectInterfaces from "./ProjectInterfaces.md?raw"; // Date Format V2 import DateFormatV2 from "./DateFormatV2.md?raw"; +import PageComplexNumbers from "./SalagataComplex.md?raw"; // ODE import ODE from "./ODE.md?raw"; @@ -89,6 +90,8 @@ export default { "DateFormatV2": DateFormatV2, + // Complex Numebrs + "SalagataComplex": PageComplexNumbers, // ODE "ODE": ODE, diff --git a/src/lib/extensions.js b/src/lib/extensions.js index 1d62e98a0..2e4d95472 100644 --- a/src/lib/extensions.js +++ b/src/lib/extensions.js @@ -253,6 +253,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", diff --git a/static/extensions/salagata/complex.js b/static/extensions/salagata/complex.js new file mode 100644 index 000000000..af8ef8d5b --- /dev/null +++ b/static/extensions/salagata/complex.js @@ -0,0 +1,2169 @@ +(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", + }, + }); + + const integrationsEnabled = { + jwVector: false, + jwArray: false, + dogeiscutSet: false, + } + + 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 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; + + 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(Boolean(modulus) || Boolean(phase)) { + this._fromPolar = true; + this._modulus = modulus; + this._phase = clampAngleDegrees(phase); + + switch (constrainAngleClampedPositive(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() { + if(this._fromPolar) { + return `Complex<${this.real},${this.imaginary},${this.modulus},${this.phase}>`; + } else { + return `Complex<${this.real},${this.imaginary}>`; + } + } + + /** + * 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 degrees + * @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, as a complex plane angle + * @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) + } + } + + // 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, + // 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: "#847e3f", + menuIconURI: "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHdpZHRoPSIxMjEuNjIxNjIiIGhlaWdodD0iMTIxLjYyMTYyIiB2aWV3Qm94PSIwLDAsMTIxLjYyMTYyLDEyMS42MjE2MiI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTE3OS4xODkxOSwtMTE5LjE4OTE5KSI+PGcgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjAiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCI+PHBhdGggZD0iTTE3OS4xODkxOSwxODBjMCwtMzMuNTg0ODggMjcuMjI1OTMsLTYwLjgxMDgxIDYwLjgxMDgxLC02MC44MTA4MWMzMy41ODQ4OCwwIDYwLjgxMDgxLDI3LjIyNTkzIDYwLjgxMDgxLDYwLjgxMDgxYzAsMzMuNTg0ODggLTI3LjIyNTkzLDYwLjgxMDgxIC02MC44MTA4MSw2MC44MTA4MWMtMzMuNTg0ODgsMCAtNjAuODEwODEsLTI3LjIyNTkzIC02MC44MTA4MSwtNjAuODEwODF6IiBmaWxsPSIjODQ3ZTNmIiBzdHJva2UtbGluZWNhcD0iYnV0dCIvPjxwYXRoIGQ9Ik0yNjIuMjMyNjksMTQ1LjA0NDA5YzAsNS4yMDA3NyAtNS4wMzI2Myw4LjYwNDQ2IC0xMi4zMTM3MSw4LjYwNDQ2Yy03LjI4MTA3LDAgLTEwLjc4NjUzLC0xLjEzNDE5IC0xMC43ODY1MywtNi4zMzQ5NWMwLC01LjIwMDc3IDQuNTc3NjgsLTkuODU5MjMgMTEuODU4NzUsLTkuODU5MjNjNy4yODEwOCwwIDExLjI0MTUsMi4zODg5NyAxMS4yNDE1LDcuNTg5NzR6IiBmaWxsPSIjZmZlYTAwIiBzdHJva2UtbGluZWNhcD0iYnV0dCIvPjxwYXRoIGQ9Ik0yNTEuNzQ3MDYsMTc3LjQ1Nzk4Yy0xLjY1ODEzLDMuNDYzNTggLTMuNTEyMzEsNi42ODI2OCAtNi45MDI3OCwxNC4zNjA4M2MtMy4wMTc0OCw2LjgzMzQ3IC0xMS45MjA0OCwyMi41MTA4MiAtOS45MDgzLDIzLjUxOTkyYzMuNTYwMzQsNS40MTE3MyA1LjgyMTY2LDQuODExMDMgMTMuNDkyMiw2LjU5Njk1Yy00LjE5Nzc1LDEuOTg4MDggLTIyLjAyNjk5LC0xLjUwMDUzIC0yNC41MTA2OSwtMS43MjgyNWMtMTEuNTg2NDcsLTEuMDYyMzIgLTQuNTg4MiwtMTcuMTg3OTQgMi45MTMxNSwtMzAuODg4MTJjMi4xNTEyLC0zLjkyODg4IDkuMzYyMTgsLTE1LjIyNjM4IDkuMTUzMzMsLTE4LjY4MTI3Yy0wLjQwNzg2LC02Ljc0NzExIC0xNy42OTM1OCwtNC44MzI4OCAtMTUuMzIxMTMsLTYuOTk0NzhjMi41MjM3NiwtMi4yOTk3OSAyMy41MTYyNiwtMC4xNzM0MyAyOC45MzMsMC43MzMxOWM0LjQzOTYyLDEuNDg3NjkgNi4yOTA0MSwxLjk2NjUyIDYuMjI5NTUsNC4xOTcyOGMtMC4wNzUxLDIuNzUyOTQgLTMuMTYzNzMsNi4yMjUxNyAtNC4wNzgzMyw4Ljg4NDI0eiIgZmlsbD0iI2ZmZWEwMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIi8+PC9nPjwvZz48L3N2Zz4=", + docsURI: "https://extensions.penguinmod.com/docs/SalagataComplex", + // 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: "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]"), + 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: 4 + }, + B: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1 + }, + 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: 4 + }, + B: { + type: Scratch.ArgumentType.NUMBER, + defaultValue: 1 + }, + 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 in sense of [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, + }, + color1: "#6babff", + 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: "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 + }, + }, + + 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: { + 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); + } + + 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); + 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; + // console.log(r,phi) + 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); + } + + 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() ) +})(Scratch) \ No newline at end of file diff --git a/static/images/salagata/complex_placeholder.svg b/static/images/salagata/complex_placeholder.svg new file mode 100644 index 000000000..12b010e44 --- /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