Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
import assert from 'node:assert/strict'
import { describe, it } from 'node:test'
import {
SCREEN_CANVAS,
STACKCHAN_FACE_MM,
STACKCHAN_FOOT_MM,
computeFootPlacements,
computeScreenPlane,
createRoundedRectPath,
computeStackchanKinematics,
nextLookAroundPose,
nextSpeechScale,
screenPointFromUv,
stepRotationToward,
} from './geometry.mjs'
describe('Stack-chan simulator geometry', () => {
it('defines the shishikawa Stack-chan body as a 54mm rounded cube with 4mm corners', () => {
assert.equal(STACKCHAN_FACE_MM.width, 54)
assert.equal(STACKCHAN_FACE_MM.height, 54)
assert.equal(STACKCHAN_FACE_MM.depth, 54)
assert.equal(STACKCHAN_FACE_MM.radius, 4)
})
it('defines two 24mm x 8mm x 48mm feet beneath the body', () => {
assert.equal(STACKCHAN_FOOT_MM.width, 24)
assert.equal(STACKCHAN_FOOT_MM.height, 8)
assert.equal(STACKCHAN_FOOT_MM.depth, 48)
assert.equal(STACKCHAN_FOOT_MM.count, 2)
const feet = computeFootPlacements()
assert.equal(feet.length, 2)
assert.deepEqual(
feet.map((foot) => foot.y),
[-33, -33],
)
assert.deepEqual(
feet.map((foot) => foot.x),
[-13, 13],
)
assert.deepEqual(
feet.map((foot) => foot.z),
[0, 0],
)
})
it('creates a rounded-rectangle path bounded by the 54mm square', () => {
const points = createRoundedRectPath(STACKCHAN_FACE_MM)
const xs = points.map((point) => point.x)
const ys = points.map((point) => point.y)
assert.equal(Math.min(...xs), -27)
assert.equal(Math.max(...xs), 27)
assert.equal(Math.min(...ys), -27)
assert.equal(Math.max(...ys), 27)
})
it('fits the 4:3 wasm screen canvas in front of the beveled face with a margin', () => {
const plane = computeScreenPlane({ margin: 5 })
assert.equal(SCREEN_CANVAS.width / SCREEN_CANVAS.height, 4 / 3)
assert.equal(plane.width, 44)
assert.equal(plane.height, 33)
assert.ok(plane.z > STACKCHAN_FACE_MM.depth / 2 + STACKCHAN_FACE_MM.bevelThickness)
})
it('maps Three.js screen-plane UV coordinates to Moddable canvas pixels', () => {
assert.deepEqual(screenPointFromUv({ x: 0, y: 1 }), { x: 0, y: 0 })
assert.deepEqual(screenPointFromUv({ x: 0.5, y: 0.5 }), { x: 160, y: 120 })
assert.deepEqual(screenPointFromUv({ x: 1, y: 0 }), { x: 320, y: 240 })
})
it('returns a neutral pose when look-around is disabled', () => {
assert.deepEqual(nextLookAroundPose(1234, { enabled: false }), { yaw: 0, pitch: 0, roll: 0 })
})
it('animates speech only while speaking', () => {
assert.equal(nextSpeechScale(1000, { speaking: false }), 1)
assert.ok(nextSpeechScale(1000, { speaking: true }) > 1)
})
it('keeps the feet stationary while look-around and servo motion animate only the head', () => {
const transforms = computeStackchanKinematics(1200, {
lookAround: true,
speaking: true,
motionUntil: 5800,
})
assert.notEqual(transforms.pan.rotation.y, 0)
assert.ok(transforms.head.scale.y > 1)
assert.deepEqual(transforms.feet.rotation, { x: 0, y: 0, z: 0 })
assert.deepEqual(transforms.feet.scale, { x: 1, y: 1, z: 1 })
})
it('models firmware driver rotation as pan and tilt without moving the feet', () => {
const transforms = computeStackchanKinematics(1200, {
driverRotation: { y: 0.25, p: -0.12, r: 0.04 },
lookAround: false,
speaking: false,
motionUntil: 0,
})
assert.equal(transforms.pan.rotation.y, 0.25)
assert.equal(transforms.tilt.rotation.x, -0.12)
assert.equal(transforms.head.rotation.z, 0.04)
assert.deepEqual(transforms.feet.rotation, { x: 0, y: 0, z: 0 })
})
it('limits simulated servo angular speed while tracking commanded rotation', () => {
const current = { y: 0, p: 0.2, r: 0 }
const target = { y: 1, p: -0.4, r: 0.01 }
assert.deepEqual(stepRotationToward(current, target, 0.1, 2), {
y: 0.2,
p: 0,
r: 0.01,
})
assert.deepEqual(stepRotationToward({ y: 0.9, p: -0.35, r: 0 }, target, 0.1, 2), target)
})
})
adfe118f0This file is pinned to the selected release. Download the original to open it in your design tool.