CALCULAGAMES STUDIO

Code Workspace

Write JavaScript, run it, and see what happens.

Explore games →
Not saved

Sign in or sign up to save to your library. Anyone can test code here.

Connect a coding agent

Bring your own assistant to this sandbox project. Its code changes arrive as proposals for you to review.

Save an adult library project to connect an agent.

Connect through MCP

Add this remote MCP server in your assistant’s connector settings. Choose CalculaGames sign-in when prompted, then authorize one adult project.

For ChatGPT and Claude, use a custom MCP connector where your account supports it. For Gemini, use an MCP-capable client such as Gemini CLI. Availability depends on the assistant and account; a chat message alone does not establish a connection.

Connection guide & API reference ↗

Or use a project token

For clients with an Authorization header setting. Tokens expire after 30 days and can be disconnected below. Put credentials in your client’s protected settings, not in chat or project code.

Open this panel to check your adult project connection.

Connected agents

Proposed code changes

01 / CREATE

Code editor

JavaScript
Ctrl / ⌘ + Enter to test

Your editor starts blank. Test code freely; saving requires a verified account.

02 / PLAY

Game preview

800 × 600

Untitled project

✳Your canvas is ready.

Write code on the left, then press Test game.

Ready to run

About this game

Add a full description in Project details to introduce your game and explain how to play.

LEARN AS YOU BUILD

Workspace reference

The canvas and CalculaGames helpers are ready inside your preview.

Canvas and drawing

canvas is the 800 × 600 HTML canvas. ctx is its 2D drawing context. The top-left corner is (0, 0).

ctx.fillStyle = "#148eb0";
ctx.fillRect(100, 100, 140, 90);
ctx.beginPath();
ctx.arc(400, 300, 35, 0, Math.PI * 2);
ctx.fill();

For WebGL, use GL.init(canvas) before accessing ctx or creating a 2D context. A canvas can use one context type at a time.

Mouse, touch, and animation

mouseX and mouseY are pointer coordinates on the canvas; cursorX, cursorY, and mouseDown come from cursor.js. Position updates as the pointer moves.

function draw() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.fillStyle = "#1a91ac";
  ctx.beginPath();
  ctx.arc(mouseX, mouseY, 20, 0, Math.PI * 2);
  ctx.fill();
  requestAnimationFrame(draw);
}
draw();

Press Stop test or Test game again to reset the preview.

2D helpers · SVGHelper.js

Call SVGHelper.createContext() first. Its draw method starts an animation loop.

SVGHelper.createContext();
SVGHelper.draw(() => {
  SVGHelper.background_color("white");
  SVGHelper.ellipse(cursorX, cursorY, 20, 20, "#169da3", true);
  SVGHelper.text("Hello!", 400, 80, 24, "#12485a");
});

Other helpers include line, rect, triangle, quad, arc, image, translate, rotate, and scale.

Buttons · buttons.js

Positions use proportions of the preview container: 0.5 is halfway across or down. A button appears over the canvas.

const start = new Button({
  x: 0.5, y: 0.8, label: "Start",
  onPress: () => console.log("Started!")
});
start.createButton();

Methods include setLabel, setPosition, setStyle, hide, show, and destroy.

Fields and menus

Field creates an input over the canvas. Menu and Label are available from menus.js.

const nameField = new Field({
  x: 0.5, y: 0.5, label: "Player nickname",
  onSubmit: value => console.log(value)
});
nameField.createField();

Fields also have getValue(), setValue(value), hide(), show(), and destroy().

WebGL and other helpers

WGLHelper.js supplies GL, Mat4, Mat5, Vec3, Vec4, Camera, Mesh, Scene, and more.

GL.init(canvas);
GL.clear(0.1, 0.6, 0.8, 1);

gameHelper.js adds discreetGameLoop({step, draw, stepTime}) and snapToGrid(x, y, stepX, stepY). All seven files in JS_INFRASTRUCTURE are loaded for each run.

Full documentation

Syntax, working examples, and the complete public helper reference from all seven JS_INFRASTRUCTURE files. Expand a topic below; use your browser’s Find to locate a method.

Start here · JavaScript syntax

Paste JavaScript directly into the editor; do not add HTML or <script> tags. Names are case-sensitive: Button and button differ. Run one example at a time in a new project. These helpers are ordinary JavaScript, already loaded before your code.

SyntaxHow to use it
const speed = 3;A binding you do not reassign. An object stored in const can still change its properties.
let score = 0; score += 1;A variable whose value changes.
const player = {x: 100, y: 200};An object; read or change player.x.
const points = [10, 20, 30];An array; first item is points[0]. Use points.length for its size.
function move(x) { return x + 2; }A function; call move(10) to obtain 12.
onPress: button => { score += 1; }A callback: pass a function for the helper to call later.
if (score >= 10) { /* ... */ }A condition. Use === for equality, && for AND, || for OR, ! for NOT.
for (let i = 0; i < 5; i++) { /* ... */ }Repeat a bounded number of times. An endless while loop freezes the preview.
new Button({x: 0.5, y: 0.8, label: "Go"})Construct an instance using an options object. Call its creation method to display it.
// commentNotes ignored by JavaScript. Strings use quotes; booleans are true and false.
Math.PI; Math.random(); Math.sin(angle);Built-in math. Random returns a number from 0 inclusive to 1 exclusive. Trigonometry uses radians.
Canvas, input, testing & saving
SyntaxHow to use it
canvasThe preview’s HTML canvas, initially 800 × 600. Read canvas.width and canvas.height; WebGL resizing can change them.
ctxLazy access to canvas.getContext("2d"). Use either 2D or WebGL on a canvas, not both.
mouseX, mouseYRead-only current pointer position in canvas pixels; updated on pointer move and press, including touch. Initially 0.
cursorX, cursorYLegacy cursor.js coordinates in canvas pixels, updated on mouse move / touch move. Touch start alone does not update these coordinates.
mouseDownLegacy mouse/touch held flag. Mouse release outside the canvas clears it. Prefer pointer events for custom touch controls.
trackCursor(canvas)Already called by cursor.js for gameCanvas. Calling again on the same canvas adds duplicate listeners.
requestAnimationFrame(draw)Schedule a drawing callback for the next animation frame; its time argument is milliseconds.
console.log(value)Print diagnostics to the editor output. Thrown errors also appear below the preview.
Ctrl / ⌘ + EnterTest game. Each run creates a fresh isolated preview, resetting variables, UI elements and loops.
Test game page / Save & view gameTest opens an unsaved game page. Save & view saves to your eligible account/profile library before opening the game page.

Anyone can test. Saving requires a verified account; child profiles also require confirmed parental permission. Title, brief description, and full description belong to the project. New project starts blank. Stop test removes the running preview.

The preview blocks network requests and external scripts, fonts, and remote images. Use self-contained code and data: or blob: image URLs. Normal page access, account data, and local storage are unavailable to the sandbox. No imports are needed for the seven bundled helpers.

Native 2D drawing & keyboard input
SyntaxHow to use it
ctx.fillStyle = "#168eac"Set a CSS fill color.
ctx.fillRect(x, y, width, height)Draw a filled rectangle from its top-left corner.
ctx.clearRect(0, 0, canvas.width, canvas.height)Erase the frame before redrawing.
ctx.beginPath(); ctx.arc(x, y, radius, start, end); ctx.fill();Draw a filled circle with end = Math.PI * 2. Use ctx.stroke() for an outline.
ctx.save(); ctx.restore();Save and restore styles, transforms, and clipping.
window.addEventListener("keydown", event => { ... })Read event.key or event.code. Click the preview first so keyboard events reach it. Use keyup to clear held keys.
let x = 100;
const keys = new Set();
window.addEventListener("keydown", e => { keys.add(e.key); if(e.key.startsWith("Arrow")) e.preventDefault(); });
window.addEventListener("keyup", e => keys.delete(e.key));
window.addEventListener("blur", () => keys.clear());
let last = performance.now();
function draw(now) {
  const dt = Math.min((now - last) / 1000, 0.05);
  last = now;
  if (keys.has("ArrowRight")) x += 180 * dt;
  if (keys.has("ArrowLeft")) x -= 180 * dt;
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.fillStyle = "#168eac";
  ctx.fillRect(x, 260, 60, 60);
  requestAnimationFrame(draw);
}
requestAnimationFrame(draw);
2D helper · SVGHelper.js

Despite the filename, this helper draws into a Canvas 2D context, not SVG markup. Every method below has the SVGHelper. prefix. Coordinates and widths are canvas pixels; positive y points down. Shape fill defaults to false. Styles and transforms can persist between calls.

SyntaxHow to use it
SVGHelper.createContext()Initialize the 2D context; returns the native context. Call before any other drawing helper.
SVGHelper.background_color(color)Fill the entire canvas with a CSS color.
SVGHelper.line(x1, y1, x2, y2, color = "black", width = 1)Stroke a segment.
SVGHelper.ellipse(x, y, rx, ry, color = "black", fill = false, strokeWidth = 1)Center and two radii. Set fill to true for a solid ellipse.
SVGHelper.rect(x, y, w, h, color = "black", fill = false)Rectangle anchored at its top-left corner.
SVGHelper.triangle(x1, y1, x2, y2, x3, y3, color = "black", fill = false)Three vertices; closes the path.
SVGHelper.quad(x1, y1, x2, y2, x3, y3, x4, y4, color = "black", fill = false)Four vertices in perimeter order.
SVGHelper.arc(x, y, rad, start, end, color = "black", width = 1)Stroke a circular arc; angles in radians.
SVGHelper.text(str, x, y, size = 16, color = "black", font = "Arial", align = "center")Draw text, vertically centered at y. Size is pixels; align can be left, center, or right.
SVGHelper.image(img, x, y, size, mode = "width", vertAlign = "center", horzAlign = "center", angle = 0, pivotOffset = null)Draw a loaded Image, maintaining aspect ratio. mode width sizes the width; use height for height. Vertical: top/center/bottom; horizontal: left/center/right. angle is radians. Optional pivotOffset is {x,y}, rotated with angle.
SVGHelper.save() / restore()Save/restore the underlying context state.
SVGHelper.translate(x, y)Move the drawing origin by pixels.
SVGHelper.rotate(angle)Rotate around the current origin, in radians.
SVGHelper.scale(sx, sy)Scale subsequent coordinates. Pass both axes.
SVGHelper.clipRect(x, y, w, h)Clip subsequent drawing to a rectangle. Wrap with save/restore.
SVGHelper.draw(callback)Start an animation loop calling callback with no arguments. Start only once; use Stop test to reset.
SVGHelper.createContext();
SVGHelper.draw(() => {
  SVGHelper.background_color("#ecfbff");
  SVGHelper.ellipse(mouseX, mouseY, 24, 24, "#168eac", true);
  SVGHelper.text("Follow the pointer", 400, 60, 24, "#16506c");
});
SVGHelper.createContext();
const picture = new Image();
picture.onload = () => SVGHelper.image(picture, 400, 300, 160);
picture.src = "data:image/svg+xml," + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" width="80" height="80"><circle cx="40" cy="40" r="35" fill="skyblue"/></svg>');
Buttons · buttons.js

UI controls are HTML overlays, not pixels painted into the canvas. Their x/y position is a fraction of the game container: (0,0) is top-left; (0.5,0.5) centers the control. Set x, y, and label explicitly.

SyntaxHow to use it
new Button({x, y, label, onPress, whilePressed, onRelease, style, disabled})Callbacks receive the button instance. onPress fires on click; whilePressed runs only when you call update() while held. onRelease is attached only when whilePressed exists (mouseup or mouseleave). Held behavior uses mouse events.
button.createButton()Create once and insert into .game-container.
button.update()Call each frame for held callbacks; it does not run automatically.
button.setLabel(text) / setPosition(x, y)Change text or fractional position.
button.setStyle(style)Merge style options and apply them to the element.
button.setDisabled(boolean)Toggle pointer access and opacity.
button.hide() / show() / destroy()Hide, show, or remove its DOM element. destroy allows later recreation.
button.getScale() / applyStyles(element)Width of the container in CSS pixels; lower-level style applier.
style: {width: 0.2, height: 0.08, fontSize: "18px", background: "#087ca2", color: "white"}width and height are fractions of container width (both axes). Use a CSS string for fontSize; the current helper overwrites its initial scaled font size.
style: {padding, margin, border, borderWidth, borderStyle, borderColor, fontFamily, fontWeight, borderRadius, cursor}CSS strings. A combined border takes priority over its separate components.
// The legacy resize listener expects a global buttons array.
var buttons = [];
let score = 0;
const scoreButton = new Button({
  x: 0.5, y: 0.8, label: "Score: 0",
  style: {width: 0.25, height: 0.08, fontSize: "18px", background: "#087ca2", color: "white", borderRadius: "16px"},
  onPress: button => button.setLabel("Score: " + (++score))
});
buttons.push(scoreButton);
scoreButton.createButton();

For held buttons, call buttons.forEach(button => button.update()) inside your animation loop. The legacy resize listener references buttons directly, so declaring var buttons = [] prevents an undefined-variable resize error. Remove destroyed buttons from that array.

Fields · fields.js
SyntaxHow to use it
new Field({x, y, label = "", value = "", onSubmit, onChange, style = {}})Centered fractional position, like Button. Text input and an OK button. Use a made-up nickname or game value rather than personal information.
field.createField()Create the overlay. onChange(value, field) runs on input; onSubmit(value, field) runs when OK is clicked.
field.getValue() / setValue(value)Read/set the stored value. setValue does not call onChange automatically. Input values are strings; use Number(value) when needed.
field.hide() / show() / destroy()Hide, show as flex, or remove.
field.applyStyles(element)Apply current style to an element. Supported keys: width, padding, background, color, border, borderRadius, gap. Use CSS units. The helper forces a centered column flex layout.
field.domElement / inputElementCreated wrapper and native input, available after createField().
const amount = new Field({
  x: 0.5, y: 0.5, label: "Number of stars", value: "5",
  style: {width: "220px", padding: "16px", background: "#ddf7ff", borderRadius: "12px"},
  onSubmit: value => console.log("Stars:", Number(value))
});
amount.createField();
Menus & labels · menus.js
SyntaxHow to use it
new Menu({x = 0.5, y = 0.5, width = "300px", height = "400px", title = "Menu", items = [], style = {}})An overlay panel with a header and scrolling body. buttons is also accepted instead of items. Items are Button instances or objects implementing create(parent), such as Label.
menu.create() / addItem(item)Create the panel; append items before or after creation. Menu moves buttons into its body and changes their layout to full-width rows.
menu.show() / hide() / destroy()Show/hide panel. destroy also calls destroy on its items.
menu.setStyle({container: {...}, header: {...}, body: {...}})Merge nested CSS property objects. Values use CSS strings. Internal utilities: mergeStyles(defaults, overrides), applyStyles(element, styles).
new Label({text, style = {}, className = ""})Text-only menu row. style is a CSS object. className must be one class token.
label.create(parent) / destroy()Append label to the supplied element, or remove it. Native element is label.el after creation.
var buttons = [];
const resume = new Button({x: 0.5, y: 0, label: "Resume", onPress: () => pause.hide()});
buttons.push(resume);
const pause = new Menu({
  title: "Paused", width: "280px", height: "220px",
  items: [new Label({text: "Ready to continue?", style: {color: "#14506a"}}), resume],
  style: {container: {background: "#e8faff", border: "1px solid #72c6db"}, header: {background: "#148eb0"}}
});
pause.create();

Menu/Field positions are computed when created; unlike registered buttons, they do not automatically reposition on resize. Update their DOM position or recreate them if your game changes layout.

Game timing & grids · gameHelper.js
SyntaxHow to use it
discreetGameLoop({step, draw, stepTime = 1000})Exact spelling is discreet. Fixed simulation step in milliseconds; step() can run multiple times to catch up, then draw() runs once per animation frame. Neither receives arguments. Keep stepTime positive. Start one loop per run.
snapToGrid(x, y, stepX, stepY)Returns {x,y} rounded to the closest grid intersection. Pass nonzero positive spacings. Does not mutate input or draw anything.
let point = {x: 0, y: 0};
discreetGameLoop({
  stepTime: 100,
  step: () => { point = snapToGrid(mouseX, mouseY, 40, 40); },
  draw: () => {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.fillStyle = "#168eac";
    ctx.fillRect(point.x, point.y, 20, 20);
  }
});
WebGL setup, shaders & rendering · WGLHelper.js

Initialize WebGL before reading ctx or calling SVGHelper.createContext(). WebGL is browser/device dependent. This library uses WebGL 1, flat color shaders and position-only meshes; it does not provide lighting, texture materials, physics, or a complete game engine.

SyntaxHow to use it
GL.init(canvas)Acquire WebGL context, resize viewport, return GL; throws if unavailable. GL.gl is the native context; GL.canvas, GL.width, GL.height expose current dimensions.
GL.resize()Match drawing buffer to displayed canvas size (CSS pixels), then update viewport.
GL.clear(r, g, b, a)Clear color and depth buffers. Color components use 0–1, not 0–255.
GL.enableDepthTest() / disableDepthTest()Enable/disable depth comparison. Enable for solid 3D scenes.
GL.startRenderLoop(callback) / stopRenderLoop()callback(time) receives animation-frame milliseconds; resize runs before it. Stop the current loop before starting another.
GL.createShader(type, source)Compile GLSL with GL.gl.VERTEX_SHADER or FRAGMENT_SHADER; returns shader or null and logs errors.
GL.createProgram(vertexSource, fragmentSource)Compile/link GLSL source strings; returns program or null.
GL.createDefaultShaders()Create the current model/view/projection color shader in GL.shaders.color. Call before constructing default RenderObjects. Earlier shader definition in the file is superseded by this one.
GL.shaders.colorContains program, attributes.position, and uniforms.model/view/projection/color.
GL.render(scene, cameraArgument)Calls scene.render(cameraArgument); see the controller adapter below.
GL.startSceneLoop(scene, cameraArgument, callback)Optional callback(time) updates objects before GL.render. Uses the same render loop; stop with GL.stopRenderLoop().

Current compatibility detail: RenderObject.draw reads cameraController.viewMatrix from its argument and camera.getProjectionMatrix() from a global named camera. A plain Camera instance does not supply viewMatrix. The example supplies both deliberately; use this pattern with the current library.

GL.init(canvas);
GL.enableDepthTest();
GL.createDefaultShaders();
const camera = new Camera({position: {x: 0, y: 0, z: 5}});
const view = {viewMatrix: camera.getViewMatrix()};
const scene = new Scene();
scene.clearColor = {r: 0.85, g: 0.96, b: 1, a: 1};
const cube = new RenderObject({mesh: Mesh.createCube(1.5), color: {r: 0.05, g: 0.55, b: 0.8, a: 1}});
scene.add(cube);
GL.startSceneLoop(scene, view, time => {
  cube.transform.rotation.y = time * 0.001;
  cube.transform.rotation.x = time * 0.0006;
  view.viewMatrix = camera.getViewMatrix();
});
Cameras, transforms, meshes & scenes
SyntaxHow to use it
new Camera(options = {}) / Camera.create(options)position:{x,y,z}, rotation:{x,y,z}; or x/y/z and rx/ry/rz. Defaults position/rotation 0, fov π/4 radians, near 0.1, far 1000. Looks down −Z at zero rotation.
camera.getProjectionMatrix() / getViewMatrix()Perspective from current GL aspect; inverse camera translation/rotation.
camera.screenPointToRay(x, y)Canvas-pixel coordinate to a Ray; current implementation applies X/Y rotation, not roll (Z rotation).
new Ray(origin, direction) / ray.getPoint(distance)Plain {x,y,z} vectors; direction is normalized. Returns point along ray.
new Transform()Mutable position/rotation {x,y,z}, scale {x:1,y:1,z:1}, parent, children, matrix. Angles radians.
transform.setParent(parent) / getLocalMatrix() / updateMatrix()Parent may be null. Local matrix is translation × Rz × Ry × Rx × scale. updateMatrix combines parent and recursively updates children; update roots before descendants. Avoid parenting cycles.
new Mesh({mode: GL.gl.TRIANGLES})Create GPU buffers after GL.init. Other modes include GL.gl.LINES.
mesh.uploadVertices(vertices) / uploadIndices(indices)Flat XYZ numbers and optional 16-bit unsigned vertex indices. Keep indices within 0–65535 and existing vertices.
mesh.bind(positionLocation) / draw()Bind position attribute and issue native WebGL draw; normally handled by RenderObject.
Mesh.createCube(size = 1)Centered solid cube.
Mesh.createPlane(width = 1, depth = 1)Centered plane in XZ (y = 0).
Mesh.createSphere(radius = 1, latBands = 16, lonBands = 16)Solid sphere. Positive integer band counts; keep vertex count within index limits.
new RenderObject({mesh, transform, shader, color, boundingRadius, onClick})Default transform is new Transform; shader is GL.shaders.color; color is {r:1,g:1,b:1,a:1}. boundingRadius defaults 1; selected starts false.
object.draw(controller) / intersectsRay(ray)Draw with current compatibility adapter; ray test returns a boolean for a bounding sphere, not exact triangle intersection. Update transform matrices first. Radius does not automatically follow mesh size/scale.
new Scene() / scene.add(object) / remove(object) / clear()Maintain scene.objects; add returns the object. clear removes membership, not GPU resources. scene.clearColor defaults opaque black.
scene.render(controller)Clear buffers then draw each object; does not choose or update a camera controller for you.
GL.createCameraController(options = {})Returns position, forward, up, rotation quaternion, viewMatrix and setFrame(position, rotation). Current options argument is unused. No mouse orbit listeners are installed.
controller.setFrame(position, rotation)Supply {x,y,z} position and normalized {x,y,z,w} quaternion; recomputes viewMatrix. Keep global camera projection and picking state consistent with your controller.
Vector & matrix syntax · 3D and 4D

Vec3 uses {x,y,z}; Vec4 uses {x,y,z,w}. Operations return new values. Mat4 is 16 numbers for homogeneous 3D; Mat5 is 25 numbers for homogeneous 4D. Matrix arrays are column-major. In Vec4, w is a fourth spatial coordinate when used with Mat5; in Mat4.multiplyVec4 it is the homogeneous coordinate.

SyntaxHow to use it
Mat4.identity()Return a new identity Float32Array.
Mat4.transpose(m)Return the transpose.
Mat4.multiply(a, b)Return a × b; column-major storage. Rightmost transform acts first.
Mat4.multiplyVec4(m, v)Multiply a 4×4 matrix by {x,y,z,w}; use w=1 for a point or 0 for a direction.
Mat4.translation(x, y, z)3D translation matrix.
Mat4.scale(x, y, z)Vector: multiply each component by s. Matrix: independent axis scales.
Mat4.rotationX(angle)Rotation matrix in the named axis/plane, angle in radians.
Mat4.rotationY(angle)Rotation matrix in the named axis/plane, angle in radians.
Mat4.rotationZ(angle)Rotation matrix in the named axis/plane, angle in radians.
Mat4.perspective(fov, aspect, near, far)Perspective matrix. fov in radians; aspect = width / height, 0 < near < far.
Mat5.identity()Return a new identity Float32Array.
Mat5.multiply(a, b)Return a × b; column-major storage. Rightmost transform acts first.
Mat5.transformVec4(matrix, vector)Transform a 4D {x,y,z,w} point using a 5×5 homogeneous matrix.
Mat5.rotationXY(angle)Rotation matrix in the named axis/plane, angle in radians.
Mat5.rotationXZ(angle)Rotation matrix in the named axis/plane, angle in radians.
Mat5.rotationXW(angle)Rotation matrix in the named axis/plane, angle in radians.
Mat5.rotationYZ(angle)Rotation matrix in the named axis/plane, angle in radians.
Mat5.rotationYW(angle)Rotation matrix in the named axis/plane, angle in radians.
Mat5.rotationZW(angle)Rotation matrix in the named axis/plane, angle in radians.
Vec3.create(x = 0, y = 0, z = 0)Return a plain vector object with named components.
Vec3.add(a, b)Return component-wise a + b.
Vec3.subtract(a, b)Return component-wise a − b.
Vec3.scale(v, s)Vector: multiply each component by s. Matrix: independent axis scales.
Vec3.dot(a, b)Return scalar dot product.
Vec3.length(v)Return Euclidean magnitude.
Vec3.normalize(v)Return unit vector (zero input returns a zero vector).
Vec4.create(x = 0, y = 0, z = 0, w = 0)Return a plain vector object with named components.
Vec4.add(a, b)Return component-wise a + b.
Vec4.subtract(a, b)Return component-wise a − b.
Vec4.scale(v, s)Vector: multiply each component by s. Matrix: independent axis scales.
Vec4.dot(a, b)Return scalar dot product.
Vec4.length(v)Return Euclidean magnitude.
Vec4.normalize(v)Return unit vector (zero input returns a zero vector).
const velocity = Vec3.create(1, 2, 0);
const next = Vec3.add(Vec3.create(0, 0, 0), Vec3.scale(velocity, 0.5));
console.log(next); // {x: 0.5, y: 1, z: 0}
const rotated = Mat5.transformVec4(Mat5.rotationXW(Math.PI / 4), Vec4.create(1, 0, 0, 0));
console.log(rotated);
4D projection & tesseracts
SyntaxHow to use it
GL.project4Dto3D(point, distance = 4)Project {x,y,z,w} into {x,y,z} using distance / (distance − w). Keep distance away from point.w to avoid singularities.
GL.transformVertices4D(vertices, matrix)Array of 4D point objects → new array of transformed 4D points.
GL.projectVertices4D(vertices, distance = 4)Array of 4D points → flat [x,y,z,...] 3D vertex data.
Mesh.createTesseract(size = 1)Wireframe 4D hypercube projected to 3D. Creates vertices4D, edges and cells metadata, plus updateTesseract.
mesh.updateTesseract(rotationMatrix = Mat5.identity(), projectionDistance = 4)Reproject original 4D vertices and upload line vertices. Call each animation frame to change its 4D orientation. cells contains axis, side, vertex indices and random colors; the default wireframe shader does not draw those cell colors.

To adapt the cube example, use Mesh.createTesseract(1.5) and call cube.mesh.updateTesseract(Mat5.rotationXW(time * 0.001), 4) inside its callback. This is geometric projection, not a 4D physics engine.

Picking, quaternion utilities & current limitations

GL.enablePicking(scene, camera, canvas) installs mouse/touch press handlers and expects scene.pick(camera,x,y). The current Scene class does not implement pick, so this convenience function needs your own scene.pick implementation. It is not a ready-to-use picker. For a simple custom selector, use rays and bounding spheres:

// Add after the WebGL cube example. This chooses the first hit,
// not the nearest object; adapt for overlapping objects.
canvas.addEventListener("pointerdown", () => {
  const ray = camera.screenPointToRay(mouseX, mouseY);
  for (const object of scene.objects) object.transform.updateMatrix();
  const hit = scene.objects.find(object => object.intersectsRay(ray));
  for (const object of scene.objects) object.selected = object === hit;
  if (hit && hit.onClick) hit.onClick(hit);
  console.log(hit ? "Object selected" : "No hit");
});
SyntaxHow to use it
normalize(v) / cross(a, b) / dot(a, b)Global 3D utilities. cross returns the cross product; normalize returns a zero vector for zero input.
quatFromBasis(forward, up)Construct a {x,y,z,w} orientation quaternion from nonzero, nonparallel direction vectors.
quatFromAxisAngle(axis, angle)Quaternion from a nonzero 3D axis and radians.
quatMultiply(a, b)Compose quaternions; order matters.
quatRotate(q, v)Rotate a 3D vector by a unit quaternion.
matFromQuat(q)Convert a unit quaternion to a column-major 4×4 Float32Array.

Rendering currently depends on the global camera/controller adapter explained above. Picking is approximate, has no built-in nearest-hit sorting, and does not automatically change an object’s color when selected. No automatic disposal API exists for Mesh; use Stop test to reset the preview.

Troubleshooting & choosing a helper
SyntaxHow to use it
Blank canvasCheck error output. Drawing runs only after Test game. In 2D, initialize SVGHelper before using it. In WebGL, initialize GL, shaders, camera/controller and scene.
Context is null / WebGL cannot startA previous 2D context may own the canvas. Test again and use only WebGL calls in that run. Device support also varies.
A control does not appearCall createButton(), createField(), or menu.create(). Positions for UI controls are 0–1 fractions, not pixel values.
Animation speeds up after starting againDo not start extra animation loops inside draw callbacks. Use a single loop per run.
A picture or fetch failsRemote network/image loading is blocked by the isolated preview. Use embedded data/blob images.
buttons is not defined on resizeDeclare var buttons = [] and register buttons you want repositioned.
A tap draws at an old locationUse mouseX/mouseY for the workspace’s pointer coordinates; cursorX/cursorY only update on legacy movement events.
Choosing an approachUse ctx for native 2D; SVGHelper for concise 2D shapes; Button/Field/Menu for HTML overlays; GL + Mesh + Scene for 3D; Mat5 and tesseracts for 4D projection.

Run executes in an isolated preview. Save to library requires a verified account. Guest code stays only in the current editor and is not saved.