Back to Soybert 3D
SOURCE / PINNED RELEASE

Made of little things.

Soybert 3D

Release
4bf80e758af1…
Author-recorded commit
298fa2d0e8b1…
License
LICENSE
Author’s source reference
nostr://npub1n8ga89w8h6tvwamxusfyzexw8gjy84yxu9rxgnmk955cxtml4ujswzxydd/wss%3A%2F%2Fgit.napplet.soy%2F/n-9021e1b6a96

Archive hash verified: 7083223a53bbd320…. The source-to-build association is the author’s claim; it has not been independently rebuilt.

scripts/build-title.mjs
/** Convert the installed Fredoka outlines to beveled 3D meshes at authoring time. */
import { readFile, writeFile, copyFile } from 'node:fs/promises';
import { createRequire } from 'node:module';
import opentype from 'opentype.js';
const { parse } = opentype;
import { ExtrudeGeometry, ShapePath } from 'three';
import { mergeVertices } from 'three/addons/utils/BufferGeometryUtils.js';
const require = createRequire(import.meta.url);
function pack(array) { return Buffer.from(array.buffer, array.byteOffset, array.byteLength).toString('base64'); }
async function lettering(text, weight, depth, bevel) {
  const file = await readFile(require.resolve(`@fontsource/fredoka/files/fredoka-latin-${weight}-normal.woff`));
  const font = parse(file.buffer.slice(file.byteOffset, file.byteOffset + file.byteLength));
  const shape = new ShapePath();
  for (const c of font.getPath(text, 0, 0, 1, { kerning: true }).commands) {
    if (c.type === 'M') shape.moveTo(c.x, -c.y);
    if (c.type === 'L') shape.lineTo(c.x, -c.y);
    if (c.type === 'Q') shape.quadraticCurveTo(c.x1, -c.y1, c.x, -c.y);
    if (c.type === 'C') shape.bezierCurveTo(c.x1, -c.y1, c.x2, -c.y2, c.x, -c.y);
    if (c.type === 'Z') shape.currentPath.closePath();
  }
  const geometry = new ExtrudeGeometry(shape.toShapes(), { depth, bevelEnabled: true, bevelThickness: bevel, bevelSize: bevel, bevelSegments: 2, curveSegments: 4, steps: 1 });
  geometry.computeBoundingBox(); const b = geometry.boundingBox;
  const width = b.max.x - b.min.x, height = b.max.y - b.min.y;
  geometry.translate(-(b.min.x+b.max.x)/2,-(b.min.y+b.max.y)/2,-depth/2);
  geometry.deleteAttribute('uv');
  const compact = mergeVertices(geometry, .00001);
  const normals = new Int16Array(compact.attributes.normal.array.length);
  compact.attributes.normal.array.forEach((v,i) => normals[i] = Math.round(v * 32767));
  const result = {text, width, height, positions:pack(compact.attributes.position.array), normals:pack(normals), indices:pack(new Uint32Array(compact.index.array)), groups:compact.groups};
  geometry.dispose(); compact.dispose(); return result;
}
const result = { font: '@fontsource/fredoka', title: await lettering('soybert',700,.10,.016), subtitle: await lettering('napplet.soy',700,.10,.016) };
await writeFile(new URL('../src/assets/fredoka-title.json',import.meta.url), JSON.stringify(result));
await copyFile(require.resolve('@fontsource/fredoka/LICENSE'),new URL('../art/Fredoka-LICENSE.txt',import.meta.url));
console.log('Generated real beveled Fredoka title geometry.');