history: rojo chibi bone animator prototype
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
1dc40fa22c
commit
7b0c01560c
|
|
@ -0,0 +1,164 @@
|
|||
const InkscapeNS = "http://www.inkscape.org/namespaces/inkscape";
|
||||
|
||||
const N = "(-?[\\d.eE+]+)";
|
||||
const S = "[,\\s]+";
|
||||
|
||||
const TranslateRE = new RegExp( `translate\\(\\s*${ N }${ S }${ N }\\s*\\)` );
|
||||
const MatrixRE = new RegExp( `matrix\\(\\s*${ N }${ S }${ N }${ S }${ N }${ S }${ N }${ S }${ N }${ S }${ N }\\s*\\)` );
|
||||
const RotateRE = new RegExp( `rotate\\(\\s*${ N }(?:${ S }${ N }${ S }${ N })?\\s*\\)` );
|
||||
|
||||
function applyTransformStr( s: string, x: number, y: number ): [ number, number ]
|
||||
{
|
||||
if ( null == s || "" === s ) { return [ x, y ]; }
|
||||
|
||||
let m = s.match( TranslateRE );
|
||||
|
||||
if ( null != m )
|
||||
{
|
||||
return [ x + parseFloat( m[ 1 ] ), y + parseFloat( m[ 2 ] ) ];
|
||||
}
|
||||
|
||||
m = s.match( MatrixRE );
|
||||
|
||||
if ( null != m )
|
||||
{
|
||||
const a = parseFloat( m[ 1 ] ), b = parseFloat( m[ 2 ] );
|
||||
const c = parseFloat( m[ 3 ] ), d = parseFloat( m[ 4 ] );
|
||||
const e = parseFloat( m[ 5 ] ), f = parseFloat( m[ 6 ] );
|
||||
return [ a * x + c * y + e, b * x + d * y + f ];
|
||||
}
|
||||
|
||||
m = s.match( RotateRE );
|
||||
|
||||
if ( null != m )
|
||||
{
|
||||
const angle = parseFloat( m[ 1 ] ) * Math.PI / 180;
|
||||
const cos = Math.cos( angle );
|
||||
const sin = Math.sin( angle );
|
||||
const cx = null != m[ 2 ] ? parseFloat( m[ 2 ] ) : 0;
|
||||
const cy = null != m[ 3 ] ? parseFloat( m[ 3 ] ) : 0;
|
||||
const dx = x - cx;
|
||||
const dy = y - cy;
|
||||
return [ cx + dx * cos - dy * sin, cy + dx * sin + dy * cos ];
|
||||
}
|
||||
|
||||
return [ x, y ];
|
||||
}
|
||||
|
||||
export class BoneData
|
||||
{
|
||||
group: SVGGElement = null;
|
||||
name: string = "";
|
||||
restTransform: string = "";
|
||||
pivotLocalX: number = 0;
|
||||
pivotLocalY: number = 0;
|
||||
}
|
||||
|
||||
export class RojoAnimator
|
||||
{
|
||||
svgRoot: SVGSVGElement = null;
|
||||
Bones: Map<string, BoneData> = new Map();
|
||||
|
||||
static create( svgRoot: SVGSVGElement )
|
||||
{
|
||||
let ra = new RojoAnimator();
|
||||
ra.svgRoot = svgRoot;
|
||||
ra._processBones();
|
||||
ra._stripInkscape();
|
||||
return ra;
|
||||
}
|
||||
|
||||
_label( el: Element )
|
||||
{
|
||||
return el.getAttributeNS( InkscapeNS, "label" ) ?? "";
|
||||
}
|
||||
|
||||
_processBones()
|
||||
{
|
||||
const groups = Array.from( this.svgRoot.querySelectorAll( "g" ) );
|
||||
|
||||
for ( const g of groups )
|
||||
{
|
||||
const label = this._label( g );
|
||||
|
||||
if ( !label.endsWith( "-transform" ) ) { continue; }
|
||||
|
||||
const name = label.slice( 0, label.length - "-transform".length );
|
||||
const restTransform = g.getAttribute( "transform" ) ?? "";
|
||||
const pivotLabel = name + "-pivot";
|
||||
let pivotCircle: Element = null;
|
||||
|
||||
for ( const child of Array.from( g.children ) )
|
||||
{
|
||||
if ( "circle" === child.localName && pivotLabel === this._label( child ) )
|
||||
{
|
||||
pivotCircle = child;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( null == pivotCircle ) { continue; }
|
||||
|
||||
const cx = parseFloat( pivotCircle.getAttribute( "cx" ) ?? "0" );
|
||||
const cy = parseFloat( pivotCircle.getAttribute( "cy" ) ?? "0" );
|
||||
const pivotTransform = pivotCircle.getAttribute( "transform" ) ?? "";
|
||||
const [ lx, ly ] = applyTransformStr( pivotTransform, cx, cy );
|
||||
|
||||
pivotCircle.remove();
|
||||
|
||||
const bone = new BoneData();
|
||||
bone.group = g as SVGGElement;
|
||||
bone.name = name;
|
||||
bone.restTransform = restTransform;
|
||||
bone.pivotLocalX = lx;
|
||||
bone.pivotLocalY = ly;
|
||||
|
||||
this.Bones.set( name, bone );
|
||||
}
|
||||
}
|
||||
|
||||
_stripInkscape()
|
||||
{
|
||||
const namedview = Array.from( this.svgRoot.children )
|
||||
.find( el => "namedview" === el.localName );
|
||||
|
||||
if ( null != namedview ) { namedview.remove(); }
|
||||
|
||||
const all: Element[] = [ this.svgRoot, ...Array.from( this.svgRoot.querySelectorAll( "*" ) ) ];
|
||||
|
||||
for ( const el of all )
|
||||
{
|
||||
const toRemove: string[] = [];
|
||||
|
||||
for ( let i = 0; i < el.attributes.length; i++ )
|
||||
{
|
||||
const { name } = el.attributes[ i ];
|
||||
|
||||
if ( name.startsWith( "inkscape:" ) || name.startsWith( "sodipodi:" ) )
|
||||
{
|
||||
toRemove.push( name );
|
||||
}
|
||||
}
|
||||
|
||||
for ( const attr of toRemove )
|
||||
{
|
||||
el.removeAttribute( attr );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setBoneAngle( name: string, degrees: number )
|
||||
{
|
||||
const bone = this.Bones.get( name );
|
||||
|
||||
if ( null == bone ) { return; }
|
||||
|
||||
const rest = bone.restTransform;
|
||||
const lx = bone.pivotLocalX;
|
||||
const ly = bone.pivotLocalY;
|
||||
const rotation = `translate(${ lx },${ ly }) rotate(${ degrees }) translate(${ -lx },${ -ly })`;
|
||||
const transform = "" === rest ? rotation : `${ rest } ${ rotation }`;
|
||||
|
||||
bone.group.setAttribute( "transform", transform );
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,298 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="28.966585"
|
||||
height="38.722172"
|
||||
viewBox="0 0 28.966585 38.722172"
|
||||
version="1.1"
|
||||
id="svg5"
|
||||
inkscape:version="1.4.3 (0d15f75, 2025-12-25)"
|
||||
sodipodi:docname="rojo-base.svg"
|
||||
xml:space="preserve"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||
id="namedview7"
|
||||
pagecolor="#333333"
|
||||
bordercolor="#404040"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#333333"
|
||||
inkscape:document-units="px"
|
||||
showgrid="false"
|
||||
inkscape:zoom="9.4539622"
|
||||
inkscape:cx="-14.755718"
|
||||
inkscape:cy="24.910191"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg5" /><defs
|
||||
id="defs2"><clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath7940"><rect
|
||||
style="fill:#333333;fill-opacity:1;stroke:none;stroke-width:10;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
id="rect7942"
|
||||
width="1440"
|
||||
height="810"
|
||||
x="0"
|
||||
y="0" /></clipPath></defs><g
|
||||
id="g26"
|
||||
inkscape:label="root-transform"
|
||||
transform="translate(-794.40483,-571.077)"><g
|
||||
id="g12"
|
||||
inkscape:label="body-transform"><g
|
||||
id="g11"
|
||||
inkscape:label="leg-r-transform"
|
||||
transform="translate(0.78025742,1.094282)"><rect
|
||||
style="opacity:1;fill:#e64242;fill-opacity:1;stroke:#390000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
id="rect4"
|
||||
width="2.1739705"
|
||||
height="5.6944928"
|
||||
x="810.83917"
|
||||
y="595.75208"
|
||||
ry="0.55225909"
|
||||
inkscape:label="leg-r" /><rect
|
||||
style="opacity:1;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
id="rect9"
|
||||
width="2.1332026"
|
||||
height="0.91032934"
|
||||
x="810.77997"
|
||||
y="601.66357"
|
||||
ry="0.45516467"
|
||||
inkscape:label="leg-l" /><circle
|
||||
style="opacity:1;fill:#ff01e9;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke markers fill"
|
||||
id="circle31"
|
||||
cx="812.82855"
|
||||
cy="599.16565"
|
||||
r="0.37521484"
|
||||
inkscape:label="leg-r-pivot"
|
||||
transform="translate(-0.78025742,-1.094282)" /></g><g
|
||||
id="g10"
|
||||
inkscape:label="leg-l-transform"
|
||||
transform="translate(-0.7818143,1.1896549)"><rect
|
||||
style="opacity:1;fill:#4e60da;fill-opacity:1;stroke:#390000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
id="rect3"
|
||||
width="2.1739705"
|
||||
height="5.6944928"
|
||||
x="805.39172"
|
||||
y="595.75208"
|
||||
ry="0.55225909"
|
||||
inkscape:label="leg-l" /><rect
|
||||
style="opacity:1;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
id="rect8"
|
||||
width="2.0801392"
|
||||
height="0.72460753"
|
||||
x="805.35431"
|
||||
y="601.8493"
|
||||
ry="0.36230376"
|
||||
inkscape:label="leg-l" /><circle
|
||||
style="opacity:1;fill:#ff01e9;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke markers fill"
|
||||
id="circle30"
|
||||
cx="805.76044"
|
||||
cy="599.16565"
|
||||
r="0.37521484"
|
||||
inkscape:label="leg-l-pivot"
|
||||
transform="translate(0.7818143,-1.1896549)" /></g><g
|
||||
id="g6"
|
||||
transform="matrix(-0.78898412,0.61441359,0.61441359,0.78898412,1087.3482,-367.58729)"
|
||||
inkscape:label="arm-r-transform"><g
|
||||
id="g29"
|
||||
inkscape:label="arm-r-graphics"><rect
|
||||
style="opacity:1;fill:#da4e4e;fill-opacity:1;stroke:#390000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
id="rect6"
|
||||
width="2.1739705"
|
||||
height="5.6944928"
|
||||
x="805.12994"
|
||||
y="590.17987"
|
||||
ry="0.55225909"
|
||||
inkscape:label="leg-l" /><circle
|
||||
style="opacity:1;fill:#f6e9c3;fill-opacity:1;stroke:#390000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
id="circle6"
|
||||
cx="806.1554"
|
||||
cy="596.2511"
|
||||
r="1.2602556" /></g><circle
|
||||
style="opacity:1;fill:#ff01e9;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke markers fill"
|
||||
id="circle29"
|
||||
cx="283.21512"
|
||||
cy="961.21893"
|
||||
r="0.37521484"
|
||||
inkscape:label="arm-r-pivot"
|
||||
transform="matrix(-0.78898412,0.61441359,0.61441359,0.78898412,438.93525,-341.91227)" /></g><rect
|
||||
style="opacity:1;fill:#949494;fill-opacity:1;stroke:#390000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
id="rect1"
|
||||
width="9.695344"
|
||||
height="8.6811571"
|
||||
x="804.41333"
|
||||
y="591.01855"
|
||||
ry="3.3052826"
|
||||
inkscape:label="body-graphics" /><g
|
||||
id="g5"
|
||||
transform="rotate(37.909323,800.74065,589.16456)"
|
||||
inkscape:label="arm-l-transform"><g
|
||||
id="g30"
|
||||
inkscape:label="arm-l-graphics"><rect
|
||||
style="opacity:1;fill:#4e60da;fill-opacity:1;stroke:#390000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
id="rect5"
|
||||
width="2.1739705"
|
||||
height="5.6944928"
|
||||
x="805.12994"
|
||||
y="590.17987"
|
||||
ry="0.55225909"
|
||||
inkscape:label="leg-l" /><circle
|
||||
style="opacity:1;fill:#f6e9c3;fill-opacity:1;stroke:#390000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
id="path5"
|
||||
cx="806.1554"
|
||||
cy="596.2511"
|
||||
r="1.2602556" /></g><circle
|
||||
style="opacity:1;fill:#ff01e9;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke markers fill"
|
||||
id="path26"
|
||||
cx="804.17902"
|
||||
cy="593.55609"
|
||||
r="0.37521484"
|
||||
inkscape:label="arm-l-pivot"
|
||||
transform="rotate(-37.909323,800.74065,589.16456)" /></g><g
|
||||
id="g13"
|
||||
inkscape:label="head-transform"
|
||||
transform="translate(0.1164391,1.5636286)"
|
||||
style="fill:#f6e9c3;fill-opacity:1;stroke:#390000;stroke-opacity:1"><rect
|
||||
style="display:inline;opacity:1;fill:#f6e9c3;fill-opacity:1;stroke:#390000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
id="rect7"
|
||||
width="12.503357"
|
||||
height="12.178973"
|
||||
x="803.11865"
|
||||
y="578.26825"
|
||||
ry="4.191853"
|
||||
inkscape:label="head-graphics" /><g
|
||||
id="g23"
|
||||
inkscape:label="hair-transform"
|
||||
style="display:inline"><g
|
||||
id="g31"
|
||||
inkscape:label="hair-graphics"><path
|
||||
style="opacity:1;fill:#da7300;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:fill markers stroke"
|
||||
d="m 802.71715,590.06034 1.85722,-0.95514 c 0,0 -1.03344,-5.71606 3.3704,-5.65003 1.75987,0.0264 2.43899,1.16705 2.43899,1.16705 0,0 2.58007,-1.3539 3.74746,0.52985 1.1674,1.88375 3.01917,1.06117 3.01917,1.06117 0,0 2.22866,-5.57165 -1.0082,-8.22482 -3.23687,-2.65317 -7.69419,-2.97155 -7.69419,-2.97155 l -3.68538,2.05127 c 0,0 0.81995,-2.84722 -0.93114,-2.82069 -1.75109,0.0265 -1.88375,0.66329 -1.88375,0.66329 l 0.19026,1.39279 -1.83522,0.22564 c 0,0 -4.4308,1.1674 -1.6715,1.85722 2.75929,0.68983 3.17494,0.28828 3.17494,0.28828 l -2.19632,2.92203 c 0,0 1.56842,6.63292 2.57663,8.17176 1.0082,1.53884 0.53063,0.29185 0.53063,0.29185 z"
|
||||
id="path20"
|
||||
sodipodi:nodetypes="ccscscsccscccsccscc"
|
||||
inkscape:label="hair"
|
||||
transform="translate(-0.1164391,-1.5636286)" /></g><circle
|
||||
style="opacity:1;fill:#ff01e9;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke markers fill"
|
||||
id="circle34"
|
||||
cx="809.72156"
|
||||
cy="582.39331"
|
||||
r="0.37521484"
|
||||
inkscape:label="hair-pivot" /></g><g
|
||||
id="g21"
|
||||
inkscape:label="eye-r-transform"
|
||||
transform="translate(-0.1164391,-1.5636286)"
|
||||
style="display:inline"><g
|
||||
id="g24"
|
||||
inkscape:label="eye-r-graphics"><g
|
||||
id="g15"
|
||||
transform="translate(-0.53966646,1.3539564)"
|
||||
style="display:inline"><rect
|
||||
style="fill:#ccbf9b;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke markers fill"
|
||||
id="rect20-8"
|
||||
width="2.7725384"
|
||||
height="2.4276414"
|
||||
x="811.87024"
|
||||
y="586.84436"
|
||||
ry="1.147486" /><rect
|
||||
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
id="rect13"
|
||||
width="2.8704028"
|
||||
height="3.7896748"
|
||||
x="811.81482"
|
||||
y="584.88489"
|
||||
ry="1.4352014" /><rect
|
||||
style="opacity:1;fill:#8c3532;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
id="rect14"
|
||||
width="2.0146508"
|
||||
height="2.6009443"
|
||||
x="811.84686"
|
||||
y="585.59167"
|
||||
ry="1.0412227" /><rect
|
||||
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.36252;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
id="rect15"
|
||||
width="0.81217694"
|
||||
height="0.90432769"
|
||||
x="812.11621"
|
||||
y="585.82965"
|
||||
ry="0.4476451" /></g><path
|
||||
style="fill:none;fill-opacity:1;stroke:#390000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
d="m 814.20504,586.82076 c -0.56241,-0.98423 -1.41579,-1.07305 -2.5578,-0.54632"
|
||||
id="path19"
|
||||
sodipodi:nodetypes="cc" /></g><circle
|
||||
style="opacity:1;fill:#ff01e9;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke markers fill"
|
||||
id="circle32"
|
||||
cx="812.883"
|
||||
cy="588.36292"
|
||||
r="0.37521484"
|
||||
inkscape:label="eye-r-pivot" /></g><g
|
||||
id="g22"
|
||||
inkscape:label="eye-l-transform"
|
||||
style="display:inline;fill:#f6e9c3;fill-opacity:1;stroke:#390000;stroke-opacity:1"
|
||||
transform="translate(-0.1164391,-1.5636286)"><g
|
||||
id="g25"
|
||||
inkscape:label="eye-l-graphics"><rect
|
||||
style="fill:#ccbf9b;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke markers fill"
|
||||
id="rect20"
|
||||
width="2.7725384"
|
||||
height="2.4276414"
|
||||
x="806.83789"
|
||||
y="588.34387"
|
||||
ry="1.147486" /><rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
id="rect16"
|
||||
width="2.8704028"
|
||||
height="3.7896748"
|
||||
x="806.81653"
|
||||
y="586.41492"
|
||||
ry="1.4352014" /><rect
|
||||
style="fill:#8c3532;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
id="rect17"
|
||||
width="2.0146508"
|
||||
height="2.6009443"
|
||||
x="807.66833"
|
||||
y="587.09528"
|
||||
ry="1.0412227" /><rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.29019;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
id="rect18"
|
||||
width="0.76906282"
|
||||
height="0.85632181"
|
||||
x="807.98566"
|
||||
y="587.43658"
|
||||
ry="0.42388204" /><path
|
||||
style="fill:none;fill-opacity:1;stroke:#390000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
d="m 806.82122,586.87365 c 0.56241,-0.98423 1.41579,-1.07305 2.5578,-0.54632"
|
||||
id="path18"
|
||||
sodipodi:nodetypes="cc" /></g><circle
|
||||
style="opacity:1;fill:#ff01e9;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke markers fill"
|
||||
id="circle33"
|
||||
cx="808.24622"
|
||||
cy="588.36292"
|
||||
inkscape:label="eye-l-pivot"
|
||||
r="0.37521484" /></g><circle
|
||||
style="opacity:1;fill:#ff01e9;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke markers fill"
|
||||
id="circle27"
|
||||
cx="809.37781"
|
||||
cy="590.43231"
|
||||
r="0.37521484"
|
||||
inkscape:label="head-pivot" /></g><circle
|
||||
style="opacity:1;fill:#ff01e9;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke markers fill"
|
||||
id="circle28"
|
||||
cx="809.2298"
|
||||
cy="600.00842"
|
||||
r="0.37521484"
|
||||
inkscape:label="body-pivot"
|
||||
sodipodi:insensitive="true" /></g><circle
|
||||
style="opacity:1;fill:#ff01e9;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke markers fill"
|
||||
id="circle26"
|
||||
cx="809.2298"
|
||||
cy="604.21301"
|
||||
r="0.37521484"
|
||||
inkscape:label="root-pivot" /></g></svg>
|
||||
|
After Width: | Height: | Size: 16 KiB |
|
|
@ -16,7 +16,8 @@
|
|||
<p class="subtitle">
|
||||
File tree rename/delete, locale generator rewrite, locale documentation,
|
||||
doc/ renamed to workspace/, fixed navigation header system,
|
||||
Guides/Actions workspace restructure, and Update History action rewrite.
|
||||
Guides/Actions workspace restructure, Update History action rewrite,
|
||||
and Rojo chibi bone animator prototype.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
|
|
@ -181,8 +182,6 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
<div class="card">
|
||||
<h3>Update History Action: Commit and Push</h3>
|
||||
<p>
|
||||
|
|
@ -205,6 +204,44 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>Rojo Chibi Bone Animator Prototype</h3>
|
||||
<p>
|
||||
A 2D skeletal animation prototype was started for an in-app chibi character
|
||||
intended to accompany AI agent interactions. The source artwork is an Inkscape
|
||||
SVG (<code>src/rojos/rojo-base.svg</code>) where each body part is a
|
||||
<code><g></code> labelled <code>*-transform</code> and contains a
|
||||
<code><circle></code> labelled <code>*-pivot</code> that marks the
|
||||
rotation point. The pivot circle carries an inverse transform so its
|
||||
<code>cx/cy</code> sits at the exact world-space pivot regardless of the
|
||||
bone's own transform.
|
||||
</p>
|
||||
<p style="margin-top:0.75rem">
|
||||
<code>src/rojos/rojo-animator.ts</code> provides <code>RojoAnimator</code>,
|
||||
which walks all <code>*-transform</code> groups on load, applies the pivot
|
||||
circle's transform to its <code>cx/cy</code> to recover the local-space pivot,
|
||||
removes the circles, and strips Inkscape namespaced attributes. At runtime,
|
||||
<code>setBoneAngle(name, degrees)</code> writes
|
||||
<code>restTransform translate(lx,ly) rotate(θ) translate(-lx,-ly)</code>
|
||||
directly onto the group element — no graphics library required. The parser
|
||||
handles all three transform types present in the SVG:
|
||||
<code>translate</code>, <code>matrix</code>, and <code>rotate(angle,cx,cy)</code>.
|
||||
</p>
|
||||
<p style="margin-top:0.75rem">
|
||||
<code>public/rojos/rojo-demo.html</code> fetches the SVG, injects it into
|
||||
the page, and runs a <code>requestAnimationFrame</code> loop with three
|
||||
switchable animations: <em>Idle</em> (breathing sway), <em>Walk</em>
|
||||
(legs and arms in opposite phase), and <em>Wave</em> (right arm wave).
|
||||
</p>
|
||||
<div class="tags">
|
||||
<span class="tag">src/rojos/rojo-animator.ts</span>
|
||||
<span class="tag">public/rojos/rojo-demo.html</span>
|
||||
<span class="tag">public/rojos/rojo-base.svg</span>
|
||||
<span class="tag">SVG bone animation</span>
|
||||
<span class="tag">no graphics library</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
<section>
|
||||
|
|
@ -292,7 +329,12 @@
|
|||
<code>server/localeGenerator.ts</code> — complete rewrite (per-directory output)<br>
|
||||
<code>src/locales/generated/</code> — new: generated locale classes<br>
|
||||
<code>src/locales/LocaleManager.ts</code> — <code>static $</code> changed to <code>static get $</code><br>
|
||||
<code>CLAUDE.md</code> — updated to point to <code>workspace/outline/index.html</code>
|
||||
<code>CLAUDE.md</code> — updated to point to <code>workspace/outline/index.html</code><br>
|
||||
<code>workspace/index.html</code> — updated: added Guides and Actions cards<br>
|
||||
<code>src/rojos/rojo-base.svg</code> — new: Inkscape chibi character with bone/pivot structure<br>
|
||||
<code>src/rojos/rojo-animator.ts</code> — new: SVG bone animator (pivot parser + setBoneAngle)<br>
|
||||
<code>public/rojos/rojo-base.svg</code> — new: copy of SVG for static serving<br>
|
||||
<code>public/rojos/rojo-demo.html</code> — new: interactive animation demo (idle/walk/wave)
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
<div class="card">
|
||||
<h3><a href="2026/07-July/06-Monday/index.html">Monday, 6 July 2026</a></h3>
|
||||
<p>File tree rename/delete (right-click context menu), locale generator rewrite (per-directory output, new naming conventions), locale documentation, doc/ renamed to workspace/, fixed navigation header system, Guides/Actions workspace restructure, Update History action rewrite (commit/push with confirmation).</p>
|
||||
<p>File tree rename/delete (right-click context menu), locale generator rewrite (per-directory output, new naming conventions), locale documentation, doc/ renamed to workspace/, fixed navigation header system, Guides/Actions workspace restructure, Update History action rewrite, Rojo chibi bone animator prototype (SVG pivot system, no graphics library).</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
|
|
|
|||
|
|
@ -22,14 +22,33 @@
|
|||
<div class="card">
|
||||
<h3>Outline</h3>
|
||||
<p>
|
||||
Explains Roject's goals, features and future plans and actions that are used for
|
||||
the project.
|
||||
Explains Roject's goals, features and future plans for the project.
|
||||
<br>
|
||||
|
||||
<a href="./outline/index.html">Read more about the outline</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>Guides</h3>
|
||||
<p>
|
||||
Context, rules, workflows and examples for how to do things.
|
||||
<br>
|
||||
|
||||
<a href="./guides/index.html">Read more about the guides</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>Actions</h3>
|
||||
<p>
|
||||
Named tasks and steps for recurring topics.
|
||||
<br>
|
||||
|
||||
<a href="./actions/index.html">Read more about the actions</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>History</h3>
|
||||
<p>
|
||||
|
|
@ -40,6 +59,11 @@
|
|||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
<div>
|
||||
To get the full picture, follow each page (as human or agent).
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue