diff --git a/src/bundles/curve/index.ts b/src/bundles/curve/index.ts index ceb3bff957..8e88b81b5c 100644 --- a/src/bundles/curve/index.ts +++ b/src/bundles/curve/index.ts @@ -72,3 +72,11 @@ export { y_of, z_of } from './functions'; + +// This line explicitly imports the decorators in type_interface, so esbuild doesn't remove them during tree-shaking. +// It preserves the type definitions by signaling to esbuild that the file is actively used. +export {} from './type_interface'; + +export { + type_map +} from '../../typings/type_map'; diff --git a/src/bundles/curve/type_interface.ts b/src/bundles/curve/type_interface.ts new file mode 100644 index 0000000000..e31c82fb58 --- /dev/null +++ b/src/bundles/curve/type_interface.ts @@ -0,0 +1,123 @@ +import { classDeclaration, typeDeclaration, functionDeclaration } from '../../typings/type_map'; + +@classDeclaration('Point') +export class Point {} + +@classDeclaration('AnimatedCurve') +export class AnimatedCurve{} + +@typeDeclaration('(u: number) => Point') +export class Curve {} + +@typeDeclaration('(t: number) => Curve') +export class CurveAnimation {} + +export class TypeInterface { + @functionDeclaration('duration: number, fps: number, drawer: (func: Curve) => Curve, func: (func: Curve) => Curve', 'AnimatedCurve') + animate_3D_curve() {} + + @functionDeclaration('duration: number, fps: number, drawer: (func: Curve) => Curve, func: (func: Curve) => Curve', 'AnimatedCurve') + animate_curve() {} + + @functionDeclaration('t: number', 'number') + arc() {} + + @functionDeclaration('p: Point', 'number') + b_of() {} + + @functionDeclaration('curve1: Curve, curve2: Curve', 'Curve') + connect_ends() {} + + @functionDeclaration('curve1: Curve, curve2: Curve', 'Curve') + connect_rigidly() {} + + @functionDeclaration('numPoints: number', '(func: Curve) => Curve') + draw_3D_connected() {} + + @functionDeclaration('numPoints: number', '(func: Curve) => Curve') + draw_3D_connected_full_view() {} + + @functionDeclaration('numPoints: number', '(func: Curve) => Curve') + draw_3D_connected_full_view_proportional() {} + + @functionDeclaration('numPoints: number', '(func: Curve) => Curve') + draw_3D_points() {} + + @functionDeclaration('numPoints: number', '(func: Curve) => Curve') + draw_3D_points_full_view() {} + + @functionDeclaration('numPoints: number', '(func: Curve) => Curve') + draw_3D_points_full_view_proportional() {} + + @functionDeclaration('numPoints: number', '(func: Curve) => Curve') + draw_connected() {} + + @functionDeclaration('numPoints: number', '(func: Curve) => Curve') + draw_connected_full_view() {} + + @functionDeclaration('numPoints: number', '(func: Curve) => Curve') + draw_connected_full_view_proportional() {} + + @functionDeclaration('numPoints: number', '(func: Curve) => Curve') + draw_points() {} + + @functionDeclaration('numPoints: number', '(func: Curve) => Curve') + draw_points_full_view() {} + + @functionDeclaration('numPoints: number', '(func: Curve) => Curve') + draw_points_full_view_proportional() {} + + @functionDeclaration('p: Point', 'number') + g_of() {} + + @functionDeclaration('curve: Curve', 'Curve') + invert() {} + + @functionDeclaration('x: number, y: number, z: number, r: number, g: number, b: number', 'Point') + make_3D_color_point() {} + + @functionDeclaration('x: number, y: number, z: number', 'Point') + make_3D_point() {} + + @functionDeclaration('x: number, y: number, r: number, g: number, b: number', 'Point') + make_color_point() {} + + @functionDeclaration('x: number, y: number', 'Point') + make_point() {} + + @functionDeclaration('curve: Curve', 'Curve') + put_in_standard_position() {} + + @functionDeclaration('p: Point', 'number') + r_of() {} + + @functionDeclaration('theta1: number, theta2: number, theta3: number', '(c: Curve) => Curve') + rotate_around_origin() {} + + @functionDeclaration('x: number, y: number', '(c: Curve) => Curve') + scale() {} + + @functionDeclaration('s: number', '(c: Curve) => Curve') + scale_proportional() {} + + @functionDeclaration('x0: number, y0: number, z0: number', '(c: Curve) => Curve') + translate() {} + + @functionDeclaration('t: number', 'Point') + unit_circle() {} + + @functionDeclaration('t: number', 'Point') + unit_line() {} + + @functionDeclaration('t: number', 'Curve') + unit_line_at() {} + + @functionDeclaration('p: Point', 'number') + x_of() {} + + @functionDeclaration('p: Point', 'number') + y_of() {} + + @functionDeclaration('p: Point', 'number') + z_of() {} +} diff --git a/src/bundles/rune/display.ts b/src/bundles/rune/display.ts index c0416cd976..d3479c11b9 100644 --- a/src/bundles/rune/display.ts +++ b/src/bundles/rune/display.ts @@ -1,6 +1,7 @@ import context from 'js-slang/context'; +import { functionDeclaration } from '../../typings/type_map'; import { AnaglyphRune, HollusionRune } from './functions'; -import { type DrawnRune, AnimatedRune, type Rune, NormalRune, type RuneAnimation } from './rune'; +import { AnimatedRune, NormalRune, type DrawnRune, type Rune, type RuneAnimation } from './rune'; import { throwIfNotRune } from './runes_ops'; // ============================================================================= @@ -12,103 +13,111 @@ context.moduleContexts.rune.state = { drawnRunes }; -/** - * Renders the specified Rune in a tab as a basic drawing. - * @param rune - The Rune to render - * @return {Rune} The specified Rune - * - * @category Main - */ -export function show(rune: Rune): Rune { - throwIfNotRune(show.name, rune); - drawnRunes.push(new NormalRune(rune)); - return rune; -} +class RuneDisplay { + /** + * Renders the specified Rune in a tab as a basic drawing. + * @param rune - The Rune to render + * @return {Rune} The specified Rune + * + * @category Main + */ + @functionDeclaration('rune: Rune', 'Rune') + static show(rune: Rune): Rune { + throwIfNotRune(RuneDisplay.show.name, rune); + drawnRunes.push(new NormalRune(rune)); + return rune; + } -/** - * Renders the specified Rune in a tab as an anaglyph. Use 3D glasses to view the - * anaglyph. - * @param rune - The Rune to render - * @return {Rune} The specified Rune - * - * @category Main - */ -export function anaglyph(rune: Rune): Rune { - throwIfNotRune(anaglyph.name, rune); - drawnRunes.push(new AnaglyphRune(rune)); - return rune; -} + /** + * Renders the specified Rune in a tab as an anaglyph. Use 3D glasses to view the + * anaglyph. + * @param rune - The Rune to render + * @return {Rune} The specified Rune + * + * @category Main + */ + @functionDeclaration('rune: Rune', 'Rune') + static anaglyph(rune: Rune): Rune { + throwIfNotRune(RuneDisplay.anaglyph.name, rune); + drawnRunes.push(new AnaglyphRune(rune)); + return rune; + } -/** - * Renders the specified Rune in a tab as a hollusion, using the specified - * magnitude. - * @param rune - The Rune to render - * @param {number} magnitude - The hollusion's magnitude - * @return {Rune} The specified Rune - * - * @category Main - */ -export function hollusion_magnitude(rune: Rune, magnitude: number): Rune { - throwIfNotRune(hollusion_magnitude.name, rune); - drawnRunes.push(new HollusionRune(rune, magnitude)); - return rune; -} + /** + * Renders the specified Rune in a tab as a hollusion, using the specified + * magnitude. + * @param rune - The Rune to render + * @param {number} magnitude - The hollusion's magnitude + * @return {Rune} The specified Rune + * + * @category Main + */ + @functionDeclaration('rune: Rune, magnitude: number', 'Rune') + static hollusion_magnitude(rune: Rune, magnitude: number): Rune { + throwIfNotRune(RuneDisplay.hollusion_magnitude.name, rune); + drawnRunes.push(new HollusionRune(rune, magnitude)); + return rune; + } -/** - * Renders the specified Rune in a tab as a hollusion, with a default magnitude - * of 0.1. - * @param rune - The Rune to render - * @return {Rune} The specified Rune - * - * @category Main - */ -export function hollusion(rune: Rune): Rune { - throwIfNotRune(hollusion.name, rune); - return hollusion_magnitude(rune, 0.1); -} + /** + * Renders the specified Rune in a tab as a hollusion, with a default magnitude + * of 0.1. + * @param rune - The Rune to render + * @return {Rune} The specified Rune + * + * @category Main + */ + @functionDeclaration('rune: Rune', 'Rune') + static hollusion(rune: Rune): Rune { + throwIfNotRune(RuneDisplay.hollusion.name, rune); + return RuneDisplay.hollusion_magnitude(rune, 0.1); + } -/** - * Create an animation of runes - * @param duration Duration of the entire animation in seconds - * @param fps Duration of each frame in frames per seconds - * @param func Takes in the timestamp and returns a Rune to draw - * @returns A rune animation - * - * @category Main - */ -export function animate_rune( - duration: number, - fps: number, - func: RuneAnimation -) { - const anim = new AnimatedRune(duration, fps, (n) => { - const rune = func(n); - throwIfNotRune(animate_rune.name, rune); - return new NormalRune(rune); - }); - drawnRunes.push(anim); - return anim; -} + /** + * Create an animation of runes + * @param duration Duration of the entire animation in seconds + * @param fps Duration of each frame in frames per seconds + * @param func Takes in the timestamp and returns a Rune to draw + * @returns A rune animation + * + * @category Main + */ + @functionDeclaration('duration: number, fps: number, func: RuneAnimation', 'AnimatedRune') + static animate_rune(duration: number, fps: number, func: RuneAnimation) { + const anim = new AnimatedRune(duration, fps, (n) => { + const rune = func(n); + throwIfNotRune(RuneDisplay.animate_rune.name, rune); + return new NormalRune(rune); + }); + drawnRunes.push(anim); + return anim; + } -/** - * Create an animation of anaglyph runes - * @param duration Duration of the entire animation in seconds - * @param fps Duration of each frame in frames per seconds - * @param func Takes in the timestamp and returns a Rune to draw - * @returns A rune animation - * - * @category Main - */ -export function animate_anaglyph( - duration: number, - fps: number, - func: RuneAnimation -) { - const anim = new AnimatedRune(duration, fps, (n) => { - const rune = func(n); - throwIfNotRune(animate_anaglyph.name, rune); - return new AnaglyphRune(rune); - }); - drawnRunes.push(anim); - return anim; + /** + * Create an animation of anaglyph runes + * @param duration Duration of the entire animation in seconds + * @param fps Duration of each frame in frames per seconds + * @param func Takes in the timestamp and returns a Rune to draw + * @returns A rune animation + * + * @category Main + */ + @functionDeclaration('duration: number, fps: number, func: RuneAnimation', 'AnimatedRune') + static animate_anaglyph(duration: number, fps: number, func: RuneAnimation) { + const anim = new AnimatedRune(duration, fps, (n) => { + const rune = func(n); + throwIfNotRune(RuneDisplay.animate_anaglyph.name, rune); + return new AnaglyphRune(rune); + }); + drawnRunes.push(anim); + return anim; + } } + +export const {show, + anaglyph, + hollusion, + hollusion_magnitude, + animate_rune, + animate_anaglyph, +} = RuneDisplay; diff --git a/src/bundles/rune/functions.ts b/src/bundles/rune/functions.ts index 6e85f95e40..338ba074ab 100644 --- a/src/bundles/rune/functions.ts +++ b/src/bundles/rune/functions.ts @@ -1,4 +1,8 @@ import { mat4, vec3 } from 'gl-matrix'; +import { + functionDeclaration, + variableDeclaration, +} from '../../typings/type_map'; import { Rune, DrawnRune, @@ -37,650 +41,695 @@ export type RuneModuleState = { // Basic Runes // ============================================================================= -/** - * Rune with the shape of a full square - * - * @category Primitive - */ -export const square: Rune = getSquare(); -/** - * Rune with the shape of a blank square - * - * @category Primitive - */ -export const blank: Rune = getBlank(); -/** - * Rune with the shape of a - * small square inside a large square, - * each diagonally split into a - * black and white half - * - * @category Primitive - */ -export const rcross: Rune = getRcross(); -/** - * Rune with the shape of a sail - * - * @category Primitive - */ -export const sail: Rune = getSail(); -/** - * Rune with the shape of a triangle - * - * @category Primitive - */ -export const triangle: Rune = getTriangle(); -/** - * Rune with black triangle, - * filling upper right corner - * - * @category Primitive - */ -export const corner: Rune = getCorner(); -/** - * Rune with the shape of two overlapping - * triangles, residing in the upper half - * of the shape - * - * @category Primitive - */ -export const nova: Rune = getNova(); -/** - * Rune with the shape of a circle - * - * @category Primitive - */ -export const circle: Rune = getCircle(); -/** - * Rune with the shape of a heart - * - * @category Primitive - */ -export const heart: Rune = getHeart(); -/** - * Rune with the shape of a pentagram - * - * @category Primitive - */ -export const pentagram: Rune = getPentagram(); -/** - * Rune with the shape of a ribbon - * winding outwards in an anticlockwise spiral - * - * @category Primitive - */ -export const ribbon: Rune = getRibbon(); +class RuneFunctions { + /** + * Rune with the shape of a full square + * + * @category Primitive + */ + @variableDeclaration('Rune') + static square: Rune = getSquare(); + /** + * Rune with the shape of a blank square + * + * @category Primitive + */ + @variableDeclaration('Rune') + static blank: Rune = getBlank(); + /** + * Rune with the shape of a + * small square inside a large square, + * each diagonally split into a + * black and white half + * + * @category Primitive + */ + @variableDeclaration('Rune') + static rcross: Rune = getRcross(); + /** + * Rune with the shape of a sail + * + * @category Primitive + */ + @variableDeclaration('Rune') + static sail: Rune = getSail(); + /** + * Rune with the shape of a triangle + * + * @category Primitive + */ + @variableDeclaration('Rune') + static triangle: Rune = getTriangle(); + /** + * Rune with black triangle, + * filling upper right corner + * + * @category Primitive + */ + @variableDeclaration('Rune') + static corner: Rune = getCorner(); + /** + * Rune with the shape of two overlapping + * triangles, residing in the upper half + * of the shape + * + * @category Primitive + */ + @variableDeclaration('Rune') + static nova: Rune = getNova(); + /** + * Rune with the shape of a circle + * + * @category Primitive + */ + @variableDeclaration('Rune') + static circle: Rune = getCircle(); + /** + * Rune with the shape of a heart + * + * @category Primitive + */ + @variableDeclaration('Rune') + static heart: Rune = getHeart(); + /** + * Rune with the shape of a pentagram + * + * @category Primitive + */ + @variableDeclaration('Rune') + static pentagram: Rune = getPentagram(); + /** + * Rune with the shape of a ribbon + * winding outwards in an anticlockwise spiral + * + * @category Primitive + */ + @variableDeclaration('Rune') + static ribbon: Rune = getRibbon(); + + // ============================================================================= + // Textured Runes + // ============================================================================= + /** + * Create a rune using the image provided in the url + * @param {string} imageUrl URL to the image that is used to create the rune. + * Note that the url must be from a domain that allows CORS. + * @returns {Rune} Rune created using the image. + * + * @category Main + */ + @functionDeclaration('imageUrl: string', 'Rune') + static from_url(imageUrl: string): Rune { + const rune = getSquare(); + rune.texture = new Image(); + rune.texture.crossOrigin = 'anonymous'; + rune.texture.src = imageUrl; + return rune; + } -// ============================================================================= -// Textured Runes -// ============================================================================= -/** - * Create a rune using the image provided in the url - * @param {string} imageUrl URL to the image that is used to create the rune. - * Note that the url must be from a domain that allows CORS. - * @returns {Rune} Rune created using the image. - * - * @category Main - */ -export function from_url(imageUrl: string): Rune { - const rune = getSquare(); - rune.texture = new Image(); - rune.texture.crossOrigin = 'anonymous'; - rune.texture.src = imageUrl; - return rune; -} + // ============================================================================= + // XY-axis Transformation functions + // ============================================================================= + + /** + * Scales a given Rune by separate factors in x and y direction + * @param {number} ratio_x - Scaling factor in x direction + * @param {number} ratio_y - Scaling factor in y direction + * @param {Rune} rune - Given Rune + * @return {Rune} Resulting scaled Rune + * + * @category Main + */ + @functionDeclaration('ratio_x: number, ratio_y: number, rune: Rune', 'Rune') + static scale_independent( + ratio_x: number, + ratio_y: number, + rune: Rune + ): Rune { + throwIfNotRune(RuneFunctions.scale_independent.name, rune); + const scaleVec = vec3.fromValues(ratio_x, ratio_y, 1); + const scaleMat = mat4.create(); + mat4.scale(scaleMat, scaleMat, scaleVec); + + const wrapperMat = mat4.create(); + mat4.multiply(wrapperMat, scaleMat, wrapperMat); + return Rune.of({ + subRunes: [rune], + transformMatrix: wrapperMat + }); + } -// ============================================================================= -// XY-axis Transformation functions -// ============================================================================= + /** + * Scales a given Rune by a given factor in both x and y direction + * @param {number} ratio - Scaling factor + * @param {Rune} rune - Given Rune + * @return {Rune} Resulting scaled Rune + * + * @category Main + */ + @functionDeclaration('ratio: number, rune: Rune', 'Rune') + static scale(ratio: number, rune: Rune): Rune { + throwIfNotRune(RuneFunctions.scale.name, rune); + return RuneFunctions.scale_independent(ratio, ratio, rune); + } -/** - * Scales a given Rune by separate factors in x and y direction - * @param {number} ratio_x - Scaling factor in x direction - * @param {number} ratio_y - Scaling factor in y direction - * @param {Rune} rune - Given Rune - * @return {Rune} Resulting scaled Rune - * - * @category Main - */ -export function scale_independent( - ratio_x: number, - ratio_y: number, - rune: Rune -): Rune { - throwIfNotRune(scale_independent.name, rune); - const scaleVec = vec3.fromValues(ratio_x, ratio_y, 1); - const scaleMat = mat4.create(); - mat4.scale(scaleMat, scaleMat, scaleVec); - - const wrapperMat = mat4.create(); - mat4.multiply(wrapperMat, scaleMat, wrapperMat); - return Rune.of({ - subRunes: [rune], - transformMatrix: wrapperMat - }); -} + /** + * Translates a given Rune by given values in x and y direction + * @param {number} x - Translation in x direction + * @param {number} y - Translation in y direction + * @param {Rune} rune - Given Rune + * @return {Rune} Resulting translated Rune + * + * @category Main + */ + @functionDeclaration('x: number, y: number, rune: Rune', 'Rune') + static translate(x: number, y: number, rune: Rune): Rune { + throwIfNotRune(RuneFunctions.translate.name, rune); + const translateVec = vec3.fromValues(x, -y, 0); + const translateMat = mat4.create(); + mat4.translate(translateMat, translateMat, translateVec); + + const wrapperMat = mat4.create(); + mat4.multiply(wrapperMat, translateMat, wrapperMat); + return Rune.of({ + subRunes: [rune], + transformMatrix: wrapperMat + }); + } -/** - * Scales a given Rune by a given factor in both x and y direction - * @param {number} ratio - Scaling factor - * @param {Rune} rune - Given Rune - * @return {Rune} Resulting scaled Rune - * - * @category Main - */ -export function scale(ratio: number, rune: Rune): Rune { - throwIfNotRune(scale.name, rune); - return scale_independent(ratio, ratio, rune); -} + /** + * Rotates a given Rune by a given angle, + * given in radians, in anti-clockwise direction. + * Note that parts of the Rune + * may be cropped as a result. + * @param {number} rad - Angle in radians + * @param {Rune} rune - Given Rune + * @return {Rune} Rotated Rune + * + * @category Main + */ + @functionDeclaration('rad: number, rune: Rune', 'Rune') + static rotate(rad: number, rune: Rune): Rune { + throwIfNotRune(RuneFunctions.rotate.name, rune); + const rotateMat = mat4.create(); + mat4.rotateZ(rotateMat, rotateMat, rad); + + const wrapperMat = mat4.create(); + mat4.multiply(wrapperMat, rotateMat, wrapperMat); + return Rune.of({ + subRunes: [rune], + transformMatrix: wrapperMat + }); + } -/** - * Translates a given Rune by given values in x and y direction - * @param {number} x - Translation in x direction - * @param {number} y - Translation in y direction - * @param {Rune} rune - Given Rune - * @return {Rune} Resulting translated Rune - * - * @category Main - */ -export function translate(x: number, y: number, rune: Rune): Rune { - throwIfNotRune(translate.name, rune); - const translateVec = vec3.fromValues(x, -y, 0); - const translateMat = mat4.create(); - mat4.translate(translateMat, translateMat, translateVec); - - const wrapperMat = mat4.create(); - mat4.multiply(wrapperMat, translateMat, wrapperMat); - return Rune.of({ - subRunes: [rune], - transformMatrix: wrapperMat - }); -} + /** + * Makes a new Rune from two given Runes by + * placing the first on top of the second + * such that the first one occupies frac + * portion of the height of the result and + * the second the rest + * @param {number} frac - Fraction between 0 and 1 (inclusive) + * @param {Rune} rune1 - Given Rune + * @param {Rune} rune2 - Given Rune + * @return {Rune} Resulting Rune + * + * @category Main + */ + @functionDeclaration('frac: number, rune1: Rune, rune2: Rune', 'Rune') + static stack_frac(frac: number, rune1: Rune, rune2: Rune): Rune { + throwIfNotRune(RuneFunctions.stack_frac.name, rune1); + throwIfNotRune(RuneFunctions.stack_frac.name, rune2); + + if (!(frac >= 0 && frac <= 1)) { + throw Error('stack_frac can only take fraction in [0,1].'); + } -/** - * Rotates a given Rune by a given angle, - * given in radians, in anti-clockwise direction. - * Note that parts of the Rune - * may be cropped as a result. - * @param {number} rad - Angle in radians - * @param {Rune} rune - Given Rune - * @return {Rune} Rotated Rune - * - * @category Main - */ -export function rotate(rad: number, rune: Rune): Rune { - throwIfNotRune(rotate.name, rune); - const rotateMat = mat4.create(); - mat4.rotateZ(rotateMat, rotateMat, rad); - - const wrapperMat = mat4.create(); - mat4.multiply(wrapperMat, rotateMat, wrapperMat); - return Rune.of({ - subRunes: [rune], - transformMatrix: wrapperMat - }); -} + const upper = RuneFunctions.translate(0, -(1 - frac), RuneFunctions.scale_independent(1, frac, rune1)); + const lower = RuneFunctions.translate(0, frac, RuneFunctions.scale_independent(1, 1 - frac, rune2)); + return Rune.of({ + subRunes: [upper, lower] + }); + } -/** - * Makes a new Rune from two given Runes by - * placing the first on top of the second - * such that the first one occupies frac - * portion of the height of the result and - * the second the rest - * @param {number} frac - Fraction between 0 and 1 (inclusive) - * @param {Rune} rune1 - Given Rune - * @param {Rune} rune2 - Given Rune - * @return {Rune} Resulting Rune - * - * @category Main - */ -export function stack_frac(frac: number, rune1: Rune, rune2: Rune): Rune { - throwIfNotRune(stack_frac.name, rune1); - throwIfNotRune(stack_frac.name, rune2); - - if (!(frac >= 0 && frac <= 1)) { - throw Error('stack_frac can only take fraction in [0,1].'); - } - - const upper = translate(0, -(1 - frac), scale_independent(1, frac, rune1)); - const lower = translate(0, frac, scale_independent(1, 1 - frac, rune2)); - return Rune.of({ - subRunes: [upper, lower] - }); -} + /** + * Makes a new Rune from two given Runes by + * placing the first on top of the second, each + * occupying equal parts of the height of the + * result + * @param {Rune} rune1 - Given Rune + * @param {Rune} rune2 - Given Rune + * @return {Rune} Resulting Rune + * + * @category Main + */ + @functionDeclaration('rune1: Rune, rune2: Rune', 'Rune') + static stack(rune1: Rune, rune2: Rune): Rune { + throwIfNotRune(RuneFunctions.stack.name, rune1, rune2); + return RuneFunctions.stack_frac(1 / 2, rune1, rune2); + } -/** - * Makes a new Rune from two given Runes by - * placing the first on top of the second, each - * occupying equal parts of the height of the - * result - * @param {Rune} rune1 - Given Rune - * @param {Rune} rune2 - Given Rune - * @return {Rune} Resulting Rune - * - * @category Main - */ -export function stack(rune1: Rune, rune2: Rune): Rune { - throwIfNotRune(stack.name, rune1, rune2); - return stack_frac(1 / 2, rune1, rune2); -} + /** + * Makes a new Rune from a given Rune + * by vertically stacking n copies of it + * @param {number} n - Positive integer + * @param {Rune} rune - Given Rune + * @return {Rune} Resulting Rune + * + * @category Main + */ + @functionDeclaration('n: number, rune: Rune', 'Rune') + static stackn(n: number, rune: Rune): Rune { + throwIfNotRune(RuneFunctions.stackn.name, rune); + if (n === 1) { + return rune; + } + return RuneFunctions.stack_frac(1 / n, rune, RuneFunctions.stackn(n - 1, rune)); + } -/** - * Makes a new Rune from a given Rune - * by vertically stacking n copies of it - * @param {number} n - Positive integer - * @param {Rune} rune - Given Rune - * @return {Rune} Resulting Rune - * - * @category Main - */ -export function stackn(n: number, rune: Rune): Rune { - throwIfNotRune(stackn.name, rune); - if (n === 1) { - return rune; + /** + * Makes a new Rune from a given Rune + * by turning it a quarter-turn around the centre in + * clockwise direction. + * @param {Rune} rune - Given Rune + * @return {Rune} Resulting Rune + * + * @category Main + */ + @functionDeclaration('rune: Rune', 'Rune') + static quarter_turn_right(rune: Rune): Rune { + throwIfNotRune(RuneFunctions.quarter_turn_right.name, rune); + return RuneFunctions.rotate(-Math.PI / 2, rune); } - return stack_frac(1 / n, rune, stackn(n - 1, rune)); -} -/** - * Makes a new Rune from a given Rune - * by turning it a quarter-turn around the centre in - * clockwise direction. - * @param {Rune} rune - Given Rune - * @return {Rune} Resulting Rune - * - * @category Main - */ -export function quarter_turn_right(rune: Rune): Rune { - throwIfNotRune(quarter_turn_right.name, rune); - return rotate(-Math.PI / 2, rune); -} + /** + * Makes a new Rune from a given Rune + * by turning it a quarter-turn in + * anti-clockwise direction. + * @param {Rune} rune - Given Rune + * @return {Rune} Resulting Rune + * + * @category Main + */ + @functionDeclaration('rune: Rune', 'Rune') + static quarter_turn_left(rune: Rune): Rune { + throwIfNotRune(RuneFunctions.quarter_turn_left.name, rune); + return RuneFunctions.rotate(Math.PI / 2, rune); + } -/** - * Makes a new Rune from a given Rune - * by turning it a quarter-turn in - * anti-clockwise direction. - * @param {Rune} rune - Given Rune - * @return {Rune} Resulting Rune - * - * @category Main - */ -export function quarter_turn_left(rune: Rune): Rune { - throwIfNotRune(quarter_turn_left.name, rune); - return rotate(Math.PI / 2, rune); -} + /** + * Makes a new Rune from a given Rune + * by turning it upside-down + * @param {Rune} rune - Given Rune + * @return {Rune} Resulting Rune + * + * @category Main + */ + @functionDeclaration('rune: Rune', 'Rune') + static turn_upside_down(rune: Rune): Rune { + throwIfNotRune(RuneFunctions.turn_upside_down.name, rune); + return RuneFunctions.rotate(Math.PI, rune); + } -/** - * Makes a new Rune from a given Rune - * by turning it upside-down - * @param {Rune} rune - Given Rune - * @return {Rune} Resulting Rune - * - * @category Main - */ -export function turn_upside_down(rune: Rune): Rune { - throwIfNotRune(turn_upside_down.name, rune); - return rotate(Math.PI, rune); -} + /** + * Makes a new Rune from two given Runes by + * placing the first on the left of the second + * such that the first one occupies frac + * portion of the width of the result and + * the second the rest + * @param {number} frac - Fraction between 0 and 1 (inclusive) + * @param {Rune} rune1 - Given Rune + * @param {Rune} rune2 - Given Rune + * @return {Rune} Resulting Rune + * + * @category Main + */ + @functionDeclaration('frac: number, rune1: Rune, rune2: Rune', 'Rune') + static beside_frac(frac: number, rune1: Rune, rune2: Rune): Rune { + throwIfNotRune(RuneFunctions.beside_frac.name, rune1, rune2); + + if (!(frac >= 0 && frac <= 1)) { + throw Error('beside_frac can only take fraction in [0,1].'); + } -/** - * Makes a new Rune from two given Runes by - * placing the first on the left of the second - * such that the first one occupies frac - * portion of the width of the result and - * the second the rest - * @param {number} frac - Fraction between 0 and 1 (inclusive) - * @param {Rune} rune1 - Given Rune - * @param {Rune} rune2 - Given Rune - * @return {Rune} Resulting Rune - * - * @category Main - */ -export function beside_frac(frac: number, rune1: Rune, rune2: Rune): Rune { - throwIfNotRune(beside_frac.name, rune1, rune2); - - if (!(frac >= 0 && frac <= 1)) { - throw Error('beside_frac can only take fraction in [0,1].'); - } - - const left = translate(-(1 - frac), 0, scale_independent(frac, 1, rune1)); - const right = translate(frac, 0, scale_independent(1 - frac, 1, rune2)); - return Rune.of({ - subRunes: [left, right] - }); -} + const left = RuneFunctions.translate(-(1 - frac), 0, RuneFunctions.scale_independent(frac, 1, rune1)); + const right = RuneFunctions.translate(frac, 0, RuneFunctions.scale_independent(1 - frac, 1, rune2)); + return Rune.of({ + subRunes: [left, right] + }); + } -/** - * Makes a new Rune from two given Runes by - * placing the first on the left of the second, - * both occupying equal portions of the width - * of the result - * @param {Rune} rune1 - Given Rune - * @param {Rune} rune2 - Given Rune - * @return {Rune} Resulting Rune - * - * @category Main - */ -export function beside(rune1: Rune, rune2: Rune): Rune { - throwIfNotRune(beside.name, rune1, rune2); - return beside_frac(1 / 2, rune1, rune2); -} + /** + * Makes a new Rune from two given Runes by + * placing the first on the left of the second, + * both occupying equal portions of the width + * of the result + * @param {Rune} rune1 - Given Rune + * @param {Rune} rune2 - Given Rune + * @return {Rune} Resulting Rune + * + * @category Main + */ + @functionDeclaration('rune1: Rune, rune2: Rune', 'Rune') + static beside(rune1: Rune, rune2: Rune): Rune { + throwIfNotRune(RuneFunctions.beside.name, rune1, rune2); + return RuneFunctions.beside_frac(1 / 2, rune1, rune2); + } -/** - * Makes a new Rune from a given Rune by - * flipping it around a horizontal axis, - * turning it upside down - * @param {Rune} rune - Given Rune - * @return {Rune} Resulting Rune - * - * @category Main - */ -export function flip_vert(rune: Rune): Rune { - throwIfNotRune(flip_vert.name, rune); - return scale_independent(1, -1, rune); -} + /** + * Makes a new Rune from a given Rune by + * flipping it around a horizontal axis, + * turning it upside down + * @param {Rune} rune - Given Rune + * @return {Rune} Resulting Rune + * + * @category Main + */ + @functionDeclaration('rune: Rune', 'Rune') + static flip_vert(rune: Rune): Rune { + throwIfNotRune(RuneFunctions.flip_vert.name, rune); + return RuneFunctions.scale_independent(1, -1, rune); + } -/** - * Makes a new Rune from a given Rune by - * flipping it around a vertical axis, - * creating a mirror image - * @param {Rune} rune - Given Rune - * @return {Rune} Resulting Rune - * - * @category Main - */ -export function flip_horiz(rune: Rune): Rune { - throwIfNotRune(flip_horiz.name, rune); - return scale_independent(-1, 1, rune); -} + /** + * Makes a new Rune from a given Rune by + * flipping it around a vertical axis, + * creating a mirror image + * @param {Rune} rune - Given Rune + * @return {Rune} Resulting Rune + * + * @category Main + */ + @functionDeclaration('rune: Rune', 'Rune') + static flip_horiz(rune: Rune): Rune { + throwIfNotRune(RuneFunctions.flip_horiz.name, rune); + return RuneFunctions.scale_independent(-1, 1, rune); + } -/** - * Makes a new Rune from a given Rune by - * arranging into a square for copies of the - * given Rune in different orientations - * @param {Rune} rune - Given Rune - * @return {Rune} Resulting Rune - * - * @category Main - */ -export function make_cross(rune: Rune): Rune { - throwIfNotRune(make_cross.name, rune); - return stack( - beside(quarter_turn_right(rune), rotate(Math.PI, rune)), - beside(rune, rotate(Math.PI / 2, rune)) - ); -} + /** + * Makes a new Rune from a given Rune by + * arranging into a square for copies of the + * given Rune in different orientations + * @param {Rune} rune - Given Rune + * @return {Rune} Resulting Rune + * + * @category Main + */ + @functionDeclaration('rune: Rune', 'Rune') + static make_cross(rune: Rune): Rune { + throwIfNotRune(RuneFunctions.make_cross.name, rune); + return RuneFunctions.stack( + RuneFunctions.beside(RuneFunctions.quarter_turn_right(rune), RuneFunctions.rotate(Math.PI, rune)), + RuneFunctions.beside(rune, RuneFunctions.rotate(Math.PI / 2, rune)) + ); + } -/** - * Applies a given function n times to an initial value - * @param {number} n - A non-negative integer - * @param {function} pattern - Unary function from Rune to Rune - * @param {Rune} initial - The initial Rune - * @return {Rune} - Result of n times application of pattern to initial: - * pattern(pattern(...pattern(pattern(initial))...)) - * - * @category Main - */ -export function repeat_pattern( - n: number, - pattern: (a: Rune) => Rune, - initial: Rune -): Rune { - if (n === 0) { - return initial; - } - return pattern(repeat_pattern(n - 1, pattern, initial)); -} + /** + * Applies a given function n times to an initial value + * @param {number} n - A non-negative integer + * @param {function} pattern - Unary function from Rune to Rune + * @param {Rune} initial - The initial Rune + * @return {Rune} - Result of n times application of pattern to initial: + * pattern(pattern(...pattern(pattern(initial))...)) + * + * @category Main + */ + @functionDeclaration('n: number, pattern: (a: Rune) => Rune, initial: Rune', 'Rune') + static repeat_pattern( + n: number, + pattern: (a: Rune) => Rune, + initial: Rune + ): Rune { + if (n === 0) { + return initial; + } + return pattern(RuneFunctions.repeat_pattern(n - 1, pattern, initial)); + } -// ============================================================================= -// Z-axis Transformation functions -// ============================================================================= + // ============================================================================= + // Z-axis Transformation functions + // ============================================================================= + + /** + * The depth range of the z-axis of a rune is [0,-1], this function gives a [0, -frac] of the depth range to rune1 and the rest to rune2. + * @param {number} frac - Fraction between 0 and 1 (inclusive) + * @param {Rune} rune1 - Given Rune + * @param {Rune} rune2 - Given Rune + * @return {Rune} Resulting Rune + * + * @category Main + */ + @functionDeclaration('frac: number, rune1: Rune, rune2: Rune', 'Rune') + static overlay_frac(frac: number, rune1: Rune, rune2: Rune): Rune { + // to developer: please read https://www.tutorialspoint.com/webgl/webgl_basics.htm to understand the webgl z-axis interpretation. + // The key point is that positive z is closer to the screen. Hence, the image at the back should have smaller z value. Primitive runes have z = 0. + throwIfNotRune(RuneFunctions.overlay_frac.name, rune1); + throwIfNotRune(RuneFunctions.overlay_frac.name, rune2); + if (!(frac >= 0 && frac <= 1)) { + throw Error('overlay_frac can only take fraction in [0,1].'); + } + // by definition, when frac == 0 or 1, the back rune will overlap with the front rune. + // however, this would cause graphical glitch because overlapping is physically impossible + // we hack this problem by clipping the frac input from [0,1] to [1E-6, 1-1E-6] + // this should not be graphically noticable + let useFrac = frac; + const minFrac = 0.000001; + const maxFrac = 1 - minFrac; + if (useFrac < minFrac) { + useFrac = minFrac; + } + if (useFrac > maxFrac) { + useFrac = maxFrac; + } -/** - * The depth range of the z-axis of a rune is [0,-1], this function gives a [0, -frac] of the depth range to rune1 and the rest to rune2. - * @param {number} frac - Fraction between 0 and 1 (inclusive) - * @param {Rune} rune1 - Given Rune - * @param {Rune} rune2 - Given Rune - * @return {Rune} Resulting Rune - * - * @category Main - */ -export function overlay_frac(frac: number, rune1: Rune, rune2: Rune): Rune { - // to developer: please read https://www.tutorialspoint.com/webgl/webgl_basics.htm to understand the webgl z-axis interpretation. - // The key point is that positive z is closer to the screen. Hence, the image at the back should have smaller z value. Primitive runes have z = 0. - throwIfNotRune(overlay_frac.name, rune1); - throwIfNotRune(overlay_frac.name, rune2); - if (!(frac >= 0 && frac <= 1)) { - throw Error('overlay_frac can only take fraction in [0,1].'); - } - // by definition, when frac == 0 or 1, the back rune will overlap with the front rune. - // however, this would cause graphical glitch because overlapping is physically impossible - // we hack this problem by clipping the frac input from [0,1] to [1E-6, 1-1E-6] - // this should not be graphically noticable - let useFrac = frac; - const minFrac = 0.000001; - const maxFrac = 1 - minFrac; - if (useFrac < minFrac) { - useFrac = minFrac; - } - if (useFrac > maxFrac) { - useFrac = maxFrac; - } - - const frontMat = mat4.create(); - // z: scale by frac - mat4.scale(frontMat, frontMat, vec3.fromValues(1, 1, useFrac)); - const front = Rune.of({ - subRunes: [rune1], - transformMatrix: frontMat - }); - - const backMat = mat4.create(); - // need to apply transformation in backwards order! - mat4.translate(backMat, backMat, vec3.fromValues(0, 0, -useFrac)); - mat4.scale(backMat, backMat, vec3.fromValues(1, 1, 1 - useFrac)); - const back = Rune.of({ - subRunes: [rune2], - transformMatrix: backMat - }); - - return Rune.of({ - subRunes: [front, back] // render front first to avoid redrawing - }); -} + const frontMat = mat4.create(); + // z: scale by frac + mat4.scale(frontMat, frontMat, vec3.fromValues(1, 1, useFrac)); + const front = Rune.of({ + subRunes: [rune1], + transformMatrix: frontMat + }); + + const backMat = mat4.create(); + // need to apply transformation in backwards order! + mat4.translate(backMat, backMat, vec3.fromValues(0, 0, -useFrac)); + mat4.scale(backMat, backMat, vec3.fromValues(1, 1, 1 - useFrac)); + const back = Rune.of({ + subRunes: [rune2], + transformMatrix: backMat + }); + + return Rune.of({ + subRunes: [front, back] // render front first to avoid redrawing + }); + } -/** - * The depth range of the z-axis of a rune is [0,-1], this function maps the depth range of rune1 and rune2 to [0,-0.5] and [-0.5,-1] respectively. - * @param {Rune} rune1 - Given Rune - * @param {Rune} rune2 - Given Rune - * @return {Rune} Resulting Runes - * - * @category Main - */ -export function overlay(rune1: Rune, rune2: Rune): Rune { - throwIfNotRune(overlay.name, rune1); - throwIfNotRune(overlay.name, rune2); - return overlay_frac(0.5, rune1, rune2); -} + /** + * The depth range of the z-axis of a rune is [0,-1], this function maps the depth range of rune1 and rune2 to [0,-0.5] and [-0.5,-1] respectively. + * @param {Rune} rune1 - Given Rune + * @param {Rune} rune2 - Given Rune + * @return {Rune} Resulting Runes + * + * @category Main + */ + @functionDeclaration('rune1: Rune, rune2: Rune', 'Rune') + static overlay(rune1: Rune, rune2: Rune): Rune { + throwIfNotRune(RuneFunctions.overlay.name, rune1); + throwIfNotRune(RuneFunctions.overlay.name, rune2); + return RuneFunctions.overlay_frac(0.5, rune1, rune2); + } -// ============================================================================= -// Color functions -// ============================================================================= + // ============================================================================= + // Color functions + // ============================================================================= + + /** + * Adds color to rune by specifying + * the red, green, blue (RGB) value, ranging from 0.0 to 1.0. + * RGB is additive: if all values are 1, the color is white, + * and if all values are 0, the color is black. + * @param {Rune} rune - The rune to add color to + * @param {number} r - Red value [0.0-1.0] + * @param {number} g - Green value [0.0-1.0] + * @param {number} b - Blue value [0.0-1.0] + * @returns {Rune} The colored Rune + * + * @category Color + */ + @functionDeclaration('rune: Rune, r: number, g: number, b: number', 'Rune') + static color(rune: Rune, r: number, g: number, b: number): Rune { + throwIfNotRune(RuneFunctions.color.name, rune); + + const colorVector = [r, g, b, 1]; + return Rune.of({ + colors: new Float32Array(colorVector), + subRunes: [rune] + }); + } -/** - * Adds color to rune by specifying - * the red, green, blue (RGB) value, ranging from 0.0 to 1.0. - * RGB is additive: if all values are 1, the color is white, - * and if all values are 0, the color is black. - * @param {Rune} rune - The rune to add color to - * @param {number} r - Red value [0.0-1.0] - * @param {number} g - Green value [0.0-1.0] - * @param {number} b - Blue value [0.0-1.0] - * @returns {Rune} The colored Rune - * - * @category Color - */ -export function color(rune: Rune, r: number, g: number, b: number): Rune { - throwIfNotRune(color.name, rune); - - const colorVector = [r, g, b, 1]; - return Rune.of({ - colors: new Float32Array(colorVector), - subRunes: [rune] - }); -} + /** + * Gives random color to the given rune. + * The color is chosen randomly from the following nine + * colors: red, pink, purple, indigo, blue, green, yellow, orange, brown + * @param {Rune} rune - The rune to color + * @returns {Rune} The colored Rune + * + * @category Color + */ + @functionDeclaration('rune: Rune', 'Rune') + static random_color(rune: Rune): Rune { + throwIfNotRune(RuneFunctions.random_color.name, rune); + const randomColor = hexToColor( + colorPalette[Math.floor(Math.random() * colorPalette.length)] + ); -/** - * Gives random color to the given rune. - * The color is chosen randomly from the following nine - * colors: red, pink, purple, indigo, blue, green, yellow, orange, brown - * @param {Rune} rune - The rune to color - * @returns {Rune} The colored Rune - * - * @category Color - */ -export function random_color(rune: Rune): Rune { - throwIfNotRune(random_color.name, rune); - const randomColor = hexToColor( - colorPalette[Math.floor(Math.random() * colorPalette.length)] - ); - - return Rune.of({ - colors: new Float32Array(randomColor), - subRunes: [rune] - }); -} + return Rune.of({ + colors: new Float32Array(randomColor), + subRunes: [rune] + }); + } -/** - * Colors the given rune red (#F44336). - * @param {Rune} rune - The rune to color - * @returns {Rune} The colored Rune - * - * @category Color - */ -export function red(rune: Rune): Rune { - throwIfNotRune(red.name, rune); - return addColorFromHex(rune, '#F44336'); -} + /** + * Colors the given rune red (#F44336). + * @param {Rune} rune - The rune to color + * @returns {Rune} The colored Rune + * + * @category Color + */ + @functionDeclaration('rune: Rune', 'Rune') + static red(rune: Rune): Rune { + throwIfNotRune(RuneFunctions.red.name, rune); + return addColorFromHex(rune, '#F44336'); + } -/** - * Colors the given rune pink (#E91E63s). - * @param {Rune} rune - The rune to color - * @returns {Rune} The colored Rune - * - * @category Color - */ -export function pink(rune: Rune): Rune { - throwIfNotRune(pink.name, rune); - return addColorFromHex(rune, '#E91E63'); -} + /** + * Colors the given rune pink (#E91E63s). + * @param {Rune} rune - The rune to color + * @returns {Rune} The colored Rune + * + * @category Color + */ + @functionDeclaration('rune: Rune', 'Rune') + static pink(rune: Rune): Rune { + throwIfNotRune(RuneFunctions.pink.name, rune); + return addColorFromHex(rune, '#E91E63'); + } -/** - * Colors the given rune purple (#AA00FF). - * @param {Rune} rune - The rune to color - * @returns {Rune} The colored Rune - * - * @category Color - */ -export function purple(rune: Rune): Rune { - throwIfNotRune(purple.name, rune); - return addColorFromHex(rune, '#AA00FF'); -} + /** + * Colors the given rune purple (#AA00FF). + * @param {Rune} rune - The rune to color + * @returns {Rune} The colored Rune + * + * @category Color + */ + @functionDeclaration('rune: Rune', 'Rune') + static purple(rune: Rune): Rune { + throwIfNotRune(RuneFunctions.purple.name, rune); + return addColorFromHex(rune, '#AA00FF'); + } -/** - * Colors the given rune indigo (#3F51B5). - * @param {Rune} rune - The rune to color - * @returns {Rune} The colored Rune - * - * @category Color - */ -export function indigo(rune: Rune): Rune { - throwIfNotRune(indigo.name, rune); - return addColorFromHex(rune, '#3F51B5'); -} + /** + * Colors the given rune indigo (#3F51B5). + * @param {Rune} rune - The rune to color + * @returns {Rune} The colored Rune + * + * @category Color + */ + @functionDeclaration('rune: Rune', 'Rune') + static indigo(rune: Rune): Rune { + throwIfNotRune(RuneFunctions.indigo.name, rune); + return addColorFromHex(rune, '#3F51B5'); + } -/** - * Colors the given rune blue (#2196F3). - * @param {Rune} rune - The rune to color - * @returns {Rune} The colored Rune - * - * @category Color - */ -export function blue(rune: Rune): Rune { - throwIfNotRune(blue.name, rune); - return addColorFromHex(rune, '#2196F3'); -} + /** + * Colors the given rune blue (#2196F3). + * @param {Rune} rune - The rune to color + * @returns {Rune} The colored Rune + * + * @category Color + */ + @functionDeclaration('rune: Rune', 'Rune') + static blue(rune: Rune): Rune { + throwIfNotRune(RuneFunctions.blue.name, rune); + return addColorFromHex(rune, '#2196F3'); + } -/** - * Colors the given rune green (#4CAF50). - * @param {Rune} rune - The rune to color - * @returns {Rune} The colored Rune - * - * @category Color - */ -export function green(rune: Rune): Rune { - throwIfNotRune(green.name, rune); - return addColorFromHex(rune, '#4CAF50'); -} + /** + * Colors the given rune green (#4CAF50). + * @param {Rune} rune - The rune to color + * @returns {Rune} The colored Rune + * + * @category Color + */ + @functionDeclaration('rune: Rune', 'Rune') + static green(rune: Rune): Rune { + throwIfNotRune(RuneFunctions.green.name, rune); + return addColorFromHex(rune, '#4CAF50'); + } -/** - * Colors the given rune yellow (#FFEB3B). - * @param {Rune} rune - The rune to color - * @returns {Rune} The colored Rune - * - * @category Color - */ -export function yellow(rune: Rune): Rune { - throwIfNotRune(yellow.name, rune); - return addColorFromHex(rune, '#FFEB3B'); -} + /** + * Colors the given rune yellow (#FFEB3B). + * @param {Rune} rune - The rune to color + * @returns {Rune} The colored Rune + * + * @category Color + */ + @functionDeclaration('rune: Rune', 'Rune') + static yellow(rune: Rune): Rune { + throwIfNotRune(RuneFunctions.yellow.name, rune); + return addColorFromHex(rune, '#FFEB3B'); + } -/** - * Colors the given rune orange (#FF9800). - * @param {Rune} rune - The rune to color - * @returns {Rune} The colored Rune - * - * @category Color - */ -export function orange(rune: Rune): Rune { - throwIfNotRune(orange.name, rune); - return addColorFromHex(rune, '#FF9800'); -} + /** + * Colors the given rune orange (#FF9800). + * @param {Rune} rune - The rune to color + * @returns {Rune} The colored Rune + * + * @category Color + */ + @functionDeclaration('rune: Rune', 'Rune') + static orange(rune: Rune): Rune { + throwIfNotRune(RuneFunctions.orange.name, rune); + return addColorFromHex(rune, '#FF9800'); + } -/** - * Colors the given rune brown. - * @param {Rune} rune - The rune to color - * @returns {Rune} The colored Rune - * - * @category Color - */ -export function brown(rune: Rune): Rune { - throwIfNotRune(brown.name, rune); - return addColorFromHex(rune, '#795548'); -} + /** + * Colors the given rune brown. + * @param {Rune} rune - The rune to color + * @returns {Rune} The colored Rune + * + * @category Color + */ + @functionDeclaration('rune: Rune', 'Rune') + static brown(rune: Rune): Rune { + throwIfNotRune(RuneFunctions.brown.name, rune); + return addColorFromHex(rune, '#795548'); + } -/** - * Colors the given rune black (#000000). - * @param {Rune} rune - The rune to color - * @returns {Rune} The colored Rune - * - * @category Color - */ -export function black(rune: Rune): Rune { - throwIfNotRune(black.name, rune); - return addColorFromHex(rune, '#000000'); -} + /** + * Colors the given rune black (#000000). + * @param {Rune} rune - The rune to color + * @returns {Rune} The colored Rune + * + * @category Color + */ + @functionDeclaration('rune: Rune', 'Rune') + static black(rune: Rune): Rune { + throwIfNotRune(RuneFunctions.black.name, rune); + return addColorFromHex(rune, '#000000'); + } -/** - * Colors the given rune white (#FFFFFF). - * @param {Rune} rune - The rune to color - * @returns {Rune} The colored Rune - * - * @category Color - */ -export function white(rune: Rune): Rune { - throwIfNotRune(white.name, rune); - return addColorFromHex(rune, '#FFFFFF'); + /** + * Colors the given rune white (#FFFFFF). + * @param {Rune} rune - The rune to color + * @returns {Rune} The colored Rune + * + * @category Color + */ + @functionDeclaration('rune: Rune', 'Rune') + static white(rune: Rune): Rune { + throwIfNotRune(RuneFunctions.white.name, rune); + return addColorFromHex(rune, '#FFFFFF'); + } } /** @hidden */ @@ -893,7 +942,7 @@ export class HollusionRune extends DrawnRune { lastTime = timeInMs; const framePos - = Math.floor(timeInMs / (period / frameCount)) % frameCount; + = Math.floor(timeInMs / (period / frameCount)) % frameCount; const fbObject = frameBuffer[framePos]; gl.clearColor(1.0, 1.0, 1.0, 1.0); // Set clear color to white, fully opaque // eslint-disable-next-line no-bitwise @@ -912,3 +961,49 @@ export class HollusionRune extends DrawnRune { /** @hidden */ export const isHollusionRune = (rune: DrawnRune): rune is HollusionRune => rune.isHollusion; + +export const { + beside, + beside_frac, + black, + blank, + blue, + brown, + circle, + color, + corner, + flip_horiz, + flip_vert, + from_url, + green, + heart, + indigo, + make_cross, + nova, + orange, + overlay, + overlay_frac, + pentagram, + pink, + purple, + quarter_turn_left, + quarter_turn_right, + random_color, + rcross, + red, + repeat_pattern, + ribbon, + rotate, + sail, + scale, + scale_independent, + square, + stack, + stack_frac, + stackn, + translate, + triangle, + turn_upside_down, + white, + yellow +} = RuneFunctions; diff --git a/src/bundles/rune/index.ts b/src/bundles/rune/index.ts index 693d91bc53..fd21ea0905 100644 --- a/src/bundles/rune/index.ts +++ b/src/bundles/rune/index.ts @@ -59,3 +59,7 @@ export { hollusion_magnitude, show } from './display'; + +export { + type_map +} from '../../typings/type_map'; diff --git a/src/bundles/rune/rune.ts b/src/bundles/rune/rune.ts index b08e12251a..c8892e5ba3 100644 --- a/src/bundles/rune/rune.ts +++ b/src/bundles/rune/rune.ts @@ -1,6 +1,7 @@ import { mat4 } from 'gl-matrix'; import { type AnimFrame, glAnimation } from '../../typings/anim_types'; import type { ReplResult } from '../../typings/type_helpers'; +import { classDeclaration } from '../../typings/type_map'; import { getWebGlFromCanvas, initShaderProgram } from './runes_webgl'; const normalVertexShader = ` @@ -57,6 +58,7 @@ void main(void) { * @field transformMatrix - A mat4 that is applied to all the vertices and the sub runes * @field subRune - A (potentially empty) list of Runes */ +@classDeclaration('Rune') export class Rune { constructor( public vertices: Float32Array, diff --git a/src/tsconfig.json b/src/tsconfig.json index dda92656e8..797aab0113 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -34,7 +34,9 @@ "verbatimModuleSyntax": true, "paths": { "js-slang/context": ["./typings/js-slang/context.d.ts"] - } + }, + "experimentalDecorators": true, + "emitDecoratorMetadata": true }, /* Specifies an array of filenames or patterns to include in the program. These filenames are resolved relative to the directory containing the tsconfig.json file. */ "include": ["."], diff --git a/src/typings/type_map/index.ts b/src/typings/type_map/index.ts new file mode 100644 index 0000000000..014138baa9 --- /dev/null +++ b/src/typings/type_map/index.ts @@ -0,0 +1,69 @@ +export const type_map : Record = {}; + +const registerType = (name: string, declaration: string) => { + if (name == 'prelude') { + type_map['prelude'] = type_map['prelude'] != undefined ? type_map['prelude'] + '\n' + declaration : declaration; + } else { + type_map[name] = declaration; + } +}; + +export const classDeclaration = (name: string) => { + return (_target: any) => { + registerType('prelude', `class ${name} {}`); + }; +}; + +export const typeDeclaration = (type: string, declaration = null) => { + return (_target: any) => { + const typeAlias = `type ${_target.name} = ${type}`; + let variableDeclaration = `const ${_target.name} = ${declaration === null ? type : declaration}`; + + switch (type) { + case 'number': + variableDeclaration = `const ${_target.name} = 0`; + break; + case 'string': + variableDeclaration = `const ${_target.name} = ''`; + break; + case 'boolean': + variableDeclaration = `const ${_target.name} = false`; + break; + case 'void': + variableDeclaration = ''; + break; + } + + registerType('prelude', `${typeAlias};\n${variableDeclaration};`); + }; +}; + +export const functionDeclaration = (paramTypes: string, returnType: string) => { + return (_target: any, propertyKey: string, _descriptor: PropertyDescriptor) => { + let returnValue = ''; + switch (returnType) { + case 'number': + returnValue = 'return 0'; + break; + case 'string': + returnValue = "return ''"; + break; + case 'boolean': + returnValue = 'return false'; + break; + case 'void': + returnValue = ''; + break; + default: + returnValue = `return ${returnType}`; + break; + } + registerType(propertyKey, `function ${propertyKey} (${paramTypes}) : ${returnType} { ${returnValue} }`); + }; +}; + +export const variableDeclaration = (type: string) => { + return (_target: any, propertyKey: string) => { + registerType(propertyKey, `const ${propertyKey}: ${type} = ${type}`); + }; +};