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.
import { BufferAttribute, BufferGeometry, Group, Mesh, MeshStandardMaterial } from 'three';
import data from './assets/fredoka-title.json';
function attribute(encoded: string, normals = false) {
const binary = atob(encoded), bytes = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
return normals ? new BufferAttribute(new Int16Array(bytes.buffer), 3, true) : new BufferAttribute(new Float32Array(bytes.buffer), 3);
}
/** User-requested splash title, extruded from the actual @fontsource/fredoka outlines. */
export function createTitle() {
const group = new Group(); group.name = 'soybert · napplet.soy';
const face = new MeshStandardMaterial({ color: '#f8d99a', roughness: .35, metalness: .08 });
const sides = new MeshStandardMaterial({ color: '#98532b', roughness: .48, metalness: .12 });
const geometries: BufferGeometry[] = [];
function mesh(source: typeof data.title, front: MeshStandardMaterial) {
const geometry = new BufferGeometry();
geometry.setAttribute('position', attribute(source.positions)); geometry.setAttribute('normal', attribute(source.normals, true));
const indexBytes = Uint8Array.from(atob(source.indices), c => c.charCodeAt(0));
geometry.setIndex(new BufferAttribute(new Uint32Array(indexBytes.buffer), 1));
source.groups.forEach(g => geometry.addGroup(g.start, g.count, g.materialIndex));
geometries.push(geometry); const object = new Mesh(geometry, [front, sides]); group.add(object); return object;
}
const title = mesh(data.title, face), subtitle = mesh(data.subtitle, face);
title.rotation.set(-.06, -.12, -.035); subtitle.rotation.copy(title.rotation);
return { group,
layout(aspect: number, height: number) {
const distance = 6, viewHeight = 2 * Math.tan(38 * Math.PI / 360) * distance;
const titleWidth = Math.min(height < 260 ? 2 : 2.4, viewHeight * aspect * .76);
const titleScale = titleWidth / data.title.width;
const subtitleScale = titleWidth * .58 / data.subtitle.width;
const titleHeight = data.title.height * titleScale;
const top = viewHeight * (height < 260 ? .30 : aspect >= .85 ? .405 : .33);
group.position.set(0, top, -distance);
title.scale.setScalar(titleScale);
subtitle.scale.setScalar(subtitleScale);
subtitle.position.y = -titleHeight * .75 - .045;
},
theme(light: boolean) {
face.color.set(light ? '#e6b455' : '#f8d99a');
},
dispose() { geometries.forEach(g => g.dispose()); face.dispose(); sides.dispose(); },
};
}
