PURPOSE
Procedural low-poly 3D cube renderer used for destructible crates. Draws a unit cube with multi-axis tumble rotation, per-face flat shading (diffuse + specular + rim), screen-position perspective tilt, painter’s-algorithm back-to-front face sorting, and a golden stroke overlay. Matches the asteroid renderer’s visual quality so crates tumble visibly in 3D rather than reading as flat sprites.
OWNS
drawCrate3D— the only exported function; draws one crate per call into a 2D canvas context.- Internal cube mesh constants:
CUBE_VERTS(8 vertices of a unit cube centered at origin, half-size 1) andCUBE_FACES(12 triangles, two per cube face, each tagged with a face normal and afaceIdxin 0–5 for per-face color variation). - Lighting constants
LIGHT(normalized direction fromLX,LY,LZ),AMBIENT,DIFFUSE,SPEC_STR,SPEC_POW,RIM_STR,RIM_POW. - Per-axis rotation helpers
rotateX,rotateY,rotateZand the composedrotateCube(X tilt then Y tilt then Z spin). - Shading helpers
dot,hexToRgb,shadeCubeFace(Lambertian diffuse + Blinn-Phong specular + Fresnel rim + per-face tint shift + damage-flash blend toward white). - The virtual camera height constant
PERSPECTIVE_HEIGHT(800 world units), kept in sync withdraw-3d-terrain.ts.
READS FROM
../core— importscamera,W,Hfor camera zoom and screen dimensions../camera— imports theCameraclass and usesCamera.toS(x, y)to convert world coordinates to screen coordinates.- Call-site arguments: world
x/y,cubeSize, currentrotAngle(continuously advancing Z spin),baseColorHex,edgeColorHex, optionalflashAmount(damage flash, default 0), optionaltiltX(default 0.5) andtiltY(default 0.3) for the per-crate fixed tilt.
PUSHES TO
- The provided
CanvasRenderingContext2Donly. All output is direct 2D canvas draw calls. MutatesfillStyle,strokeStyle,lineJoin,lineWidthinside actx.save()/ctx.restore()pair. - No game state writes, no events, no telemetry, no module-level mutable state.
DOES NOT
- Does not own or update crate game state (position, health, rotation angle, tilt axes) — caller passes those in each frame.
- Does not handle input, collision, damage logic, or destruction effects.
- Does not load textures or assets — colors come in as hex strings.
- Does not allocate persistent buffers across calls; per-call arrays for projected vertices and sorted faces are recreated each invocation.
- Does not draw any background, halo, glow, particle, or shadow — only the cube faces plus the golden overlay stroke pass.
- Does not call any other renderer (no asteroid, no terrain, no HUD interaction).
Signals
None emitted. The module is a pure draw helper with no event bus, callback, or store interaction.
Entry points
drawCrate3D(ctx, x, y, cubeSize, rotAngle, baseColorHex, edgeColorHex, flashAmount?, tiltX?, tiltY?)— the sole export. Called per crate per frame by the destructible-crate rendering layer.
Pattern notes
- Early-out: if the projected
screenSize(cubeSize times camera zoom) is below 2 pixels, the function returns immediately without drawing. - Perspective tilt is computed from the crate’s screen offset from screen center divided by
PERSPECTIVE_HEIGHT, producingperspTiltX = atan2(worldOffY, PERSPECTIVE_HEIGHT)andperspTiltY = atan2(-worldOffX, PERSPECTIVE_HEIGHT). This is added to the per-crate fixed tilt before the Z spin so crates near screen edges visibly lean toward the camera origin. - Rotation order is X tilt then Y tilt then Z spin, applied to every vertex and every face normal so backface culling and face sorting use the same rotated frame as the geometry.
- Backface cull: faces with rotated normal
z < -0.01are skipped before adding to the sort list. - Face sort is painter’s algorithm — back-to-front by average rotated Z of the three triangle vertices.
- Shading model in
shadeCubeFace: ambient + diffuse-dot-clamped + Blinn-Phong specular against half-vector(LIGHT.x, LIGHT.y, LIGHT.z + 1)+ Fresnel rim from1 - |normal.z|. Brightness is clamped to 1 before applying the per-face tint shift of(faceIdx % 3) * 0.06 - 0.06. Final RGB is clamped to 0–255 and emitted as anrgb(...)string. - Damage flash blends each channel toward 255 by
flashAmount(expected 0–1 range). - Two-pass stroke: first pass strokes each triangle with the supplied
edgeColorHexat line widthmax(0.5, 0.8 * zoom); second pass strokes the same triangles with a fixed warm overlayrgba(255, 215, 100, 0.5)atmax(0.5, 1.2 * zoom)for the golden shimmer. - Lighting constants and
PERSPECTIVE_HEIGHTare duplicated fromdraw-3d-terrain.tsintentionally so crates and terrain share the same visual look; changing one without the other will break visual consistency. - The mesh is described per-triangle rather than per-quad, with two triangles sharing an explicit
faceIdxso both halves of a cube face shade identically and receive the same per-face tint shift.