Verbeter anatomie en plaatsing van avataraccessoires (v0.4.46-beta) #1

Open
bes-r wants to merge 192 commits from bes-r/avatar-realism into main AGit
3 changed files with 15 additions and 4 deletions
Showing only changes of commit 22233c5f92 - Show all commits

View file

@ -1 +1 @@
0.3.67-beta
0.3.68-beta

View file

@ -654,6 +654,8 @@ function mountFractions(root,init){
den.onchange=show.onchange=draw;learningShell(root,[field(T('fractionRows'),den),field(T('fractionLabels'),show)],stage);draw();return{getState:()=>({...S})};
}
const clockTurnDelta=(next,previous)=>((next-previous+540)%360)-180;
const clockHourCrossing=(next,previous)=>{const delta=clockTurnDelta(next,previous);return delta>0&&next<previous?1:delta<0&&next>previous?-1:0};
const clockStepHour=(hour,step,use24)=>use24?(hour+step+24)%24:((hour-1+step+12)%12)+1;
const clock24Angle=(angle,delta)=>(angle+delta+720)%720;
const clock24Hour=(angle,minute)=>((Math.round((angle-minute/2)/30)%24)+24)%24;
function mountClock(root,init){
@ -677,12 +679,12 @@ function mountClock(root,init){
if(interactive){
const updateAnswer=()=>{face.setAttribute('aria-label',time(S.h,S.m));const answer=stage.querySelector('.clock-live-answer');if(answer){answer.innerHTML='';answer.append(timeBlocks(S.h,S.m))}stage.querySelectorAll('.clock-period .tbtn').forEach(button=>{const active=+button.dataset.period===(S.h>=12?12:0);button.classList.toggle('active',active);button.setAttribute('aria-pressed',String(active))})};
const move=e=>{const r=face.getBoundingClientRect(),dx=e.clientX-r.left-r.width/2,dy=e.clientY-r.top-r.height/2,deg=(Math.atan2(dx,-dy)*180/Math.PI+360)%360;
if(face.dataset.hand==='minute'){const wanted=Math.round(deg/6)%60,allowed=minuteValues(),distance=v=>Math.min(Math.abs(v-wanted),60-Math.abs(v-wanted));S.m=allowed.reduce((best,v)=>distance(v)<distance(best)?v:best)}
if(face.dataset.hand==='minute'){const previous=face._lastPointerAngle;if(Number.isFinite(previous)){const step=clockHourCrossing(deg,previous);if(step)S.h=clockStepHour(S.h,step,uses24())}face._lastPointerAngle=deg;const wanted=Math.round(deg/6)%60,allowed=minuteValues(),distance=v=>Math.min(Math.abs(v-wanted),60-Math.abs(v-wanted));S.m=allowed.reduce((best,v)=>distance(v)<distance(best)?v:best)}
else if(uses24()){const previous=face._lastPointerAngle;if(Number.isFinite(previous)){face._dragAngle=clock24Angle(face._dragAngle,clockTurnDelta(deg,previous));if(S.linkHands){let bestH=0,bestM=0,bestDistance=720;for(let hour=0;hour<24;hour++)minuteValues().forEach(minute=>{const angle=hour*30+minute/2,difference=Math.min(Math.abs(angle-face._dragAngle),720-Math.abs(angle-face._dragAngle));if(difference<bestDistance){bestDistance=difference;bestH=hour;bestM=minute}});S.h=bestH;S.m=bestM}else S.h=clock24Hour(face._dragAngle,S.m)}face._lastPointerAngle=deg}
else if(S.linkHands){let bestH=12,bestM=0,bestDistance=360;for(let hour=1;hour<=12;hour++)minuteValues().forEach(minute=>{const angle=(hour%12)*30+minute/2,difference=Math.min(Math.abs(angle-deg),360-Math.abs(angle-deg));if(difference<bestDistance){bestDistance=difference;bestH=hour;bestM=minute}});S.h=bestH;S.m=bestM}
else S.h=(Math.round((deg-S.m/2)/30)+11)%12+1;paint();updateAnswer()};
const clearDrag=()=>{delete face.dataset.hand;delete face._dragAngle;delete face._lastPointerAngle};
const start=(e,hand)=>{if(S.solved)return;e.preventDefault();e.stopPropagation();face.dataset.hand=hand;if(hand==='hour'&&uses24()){face._dragAngle=S.h*30+S.m/2;face._lastPointerAngle=null}face.setPointerCapture(e.pointerId);move(e)};
const start=(e,hand)=>{if(S.solved)return;e.preventDefault();e.stopPropagation();face.dataset.hand=hand;if(hand==='minute')face._lastPointerAngle=null;else if(uses24()){face._dragAngle=S.h*30+S.m/2;face._lastPointerAngle=null}face.setPointerCapture(e.pointerId);move(e)};
hourHand.onpointerdown=e=>start(e,'hour');minuteHand.onpointerdown=e=>start(e,'minute');
face.onpointermove=e=>{if(face.hasPointerCapture(e.pointerId))move(e)};
face.onpointerup=e=>{move(e);face.releasePointerCapture(e.pointerId);clearDrag()};

View file

@ -102,6 +102,8 @@ test('analoge oefenklok is bedienbaar en houdt opdrachtacties buiten instellinge
assert.ok(clockSource.includes('S.h=uses24()?0:12'), 'een 24-uursopdracht begint met een geldige invoerwaarde');
assert.ok(clockSource.includes("hourHand.onpointerdown=e=>start(e,'hour')"));
assert.ok(clockSource.includes("minuteHand.onpointerdown=e=>start(e,'minute')"));
assert.ok(clockSource.includes("if(hand==='minute')face._lastPointerAngle=null"), 'de minutenwijzer volgt zijn draairichting over twaalf uur');
assert.ok(clockSource.includes('if(step)S.h=clockStepHour(S.h,step,uses24())'), 'een volledige minutenomloop verzet de urenwijzer');
assert.ok(!clockSource.includes('face.onpointerdown='), 'de hele klok is geen sleepzone');
assert.doesNotMatch(css,/\.clock-hand::(?:before|after)/, 'geen extra sleepzones rond de wijzers');
for(const key of ['clockMorning','clockAfternoon','clockEvening','clockNight'])assert.equal((core.match(new RegExp(key+':','g'))||[]).length,2,key);
@ -110,9 +112,16 @@ test('analoge oefenklok is bedienbaar en houdt opdrachtacties buiten instellinge
assert.match(css,/clock-set-answer\{[^}]*align-items:center[^}]*justify-content:center/);
assert.match(css,/clock-period \.tbtn\.active\{[^}]*background:var\(--purple\)/);
const context={};
runInNewContext(source+';globalThis.__clock24={delta:clockTurnDelta,angle:clock24Angle,hour:clock24Hour}',context);
runInNewContext(source+';globalThis.__clock24={delta:clockTurnDelta,crossing:clockHourCrossing,step:clockStepHour,angle:clock24Angle,hour:clock24Hour}',context);
assert.equal(context.__clock24.delta(2,358),4,'vooruit over twaalf uur blijft vooruit draaien');
assert.equal(context.__clock24.delta(358,2),-4,'achteruit over twaalf uur blijft achteruit draaien');
assert.equal(context.__clock24.crossing(2,358),1,'de grote wijzer verhoogt het uur bij een voorwaartse omloop');
assert.equal(context.__clock24.crossing(358,2),-1,'de grote wijzer verlaagt het uur bij een achterwaartse omloop');
assert.equal(context.__clock24.crossing(30,20),0,'bewegen binnen hetzelfde uur verzet de kleine wijzer niet');
assert.deepEqual([
context.__clock24.step(11,1,false),context.__clock24.step(12,1,false),context.__clock24.step(1,-1,false),
context.__clock24.step(23,1,true),context.__clock24.step(0,-1,true),
],[12,1,12,0,23]);
assert.equal(context.__clock24.angle(719,2),1,'na 23 uur draait de klok door naar 0 uur');
assert.equal(context.__clock24.angle(1,-2),719,'achteruit vanaf 0 uur komt de klok bij 23 uur');
assert.deepEqual([0,11,12,23].map(hour=>context.__clock24.hour(hour*30,0)),[0,11,12,23]);