@codexstar/pi-pompom 1.4.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/extensions/pompom.ts +204 -89
- package/package.json +1 -1
package/extensions/pompom.ts
CHANGED
|
@@ -276,74 +276,113 @@ function shadeObject(hit: ReturnType<typeof getObjHit>, px: number, py: number,
|
|
|
276
276
|
const spot = Math.sin(hitNx * 10) * Math.cos(hitNy * 8);
|
|
277
277
|
if (spot > 0.6) { r = Math.max(0, r - 40); g = Math.max(0, g - 20); b = Math.max(0, b - 10); }
|
|
278
278
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
if (!isSleeping && !hasBall) {
|
|
324
|
-
const mx = bdx - lookX * 0.06, my = bdy - lookY * 0.05 - 0.06;
|
|
325
|
-
if ((Math.abs(my - (mx - 0.025) ** 2 * 20 + 0.01) < 0.01 && mx > 0 && mx < 0.05) ||
|
|
326
|
-
(Math.abs(my - (mx + 0.025) ** 2 * 20 + 0.01) < 0.01 && mx < 0 && mx > -0.05)) {
|
|
327
|
-
r = 50; g = 30; b = 40;
|
|
328
|
-
}
|
|
329
|
-
if (currentState === "excited" || currentState === "singing" || speechTimer > 0 || isTalking) {
|
|
330
|
-
const mouthOpen = (speechTimer > 0 || currentState === "singing" || isTalking)
|
|
331
|
-
? (isTalking ? talkAudioLevel * 0.04 + 0.005 : Math.abs(Math.sin(time * 12)) * 0.025)
|
|
332
|
-
: 0.015;
|
|
333
|
-
if (mx * mx + (my + 0.01) ** 2 < mouthOpen && my < -0.01) {
|
|
334
|
-
r = 240; g = 80; b = 100;
|
|
335
|
-
if (my < -0.025) { r = 255; g = 120; b = 140; }
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
}
|
|
279
|
+
let isOnFace = false;
|
|
280
|
+
if (hitObj.id === "body") {
|
|
281
|
+
let bdx = px - hitObj.x, bdy = py - hitObj.y;
|
|
282
|
+
if (isFlipping) {
|
|
283
|
+
const s = Math.sin(-flipPhase), c = Math.cos(-flipPhase);
|
|
284
|
+
const nx = bdx * c - bdy * s, ny = bdx * s + bdy * c;
|
|
285
|
+
bdx = nx; bdy = ny;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// ── Face plate: bright cream area so features pop ──
|
|
289
|
+
const faceR = Math.sqrt(bdx * bdx + bdy * bdy);
|
|
290
|
+
if (faceR < 0.22) {
|
|
291
|
+
isOnFace = true;
|
|
292
|
+
const faceMix = Math.max(0, 1.0 - faceR / 0.22);
|
|
293
|
+
r = Math.floor(r * (1 - faceMix * 0.6) + 255 * faceMix * 0.6);
|
|
294
|
+
g = Math.floor(g * (1 - faceMix * 0.6) + 252 * faceMix * 0.6);
|
|
295
|
+
b = Math.floor(b * (1 - faceMix * 0.6) + 248 * faceMix * 0.6);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// ── Blush: big rosy cheeks ──
|
|
299
|
+
const blx1 = bdx + 0.15, bly1 = bdy - 0.05;
|
|
300
|
+
const blx2 = bdx - 0.15, bly2 = bdy - 0.05;
|
|
301
|
+
const blush = Math.exp(-(blx1 * blx1 + bly1 * bly1) * 40) + Math.exp(-(blx2 * blx2 + bly2 * bly2) * 40);
|
|
302
|
+
if (!isSleeping) {
|
|
303
|
+
r = Math.floor(r * (1 - blush) + 255 * blush);
|
|
304
|
+
g = Math.floor(g * (1 - blush) + 70 * blush);
|
|
305
|
+
b = Math.floor(b * (1 - blush) + 90 * blush);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// ── Eyes: kawaii style — white sclera → colored iris → dark pupil → highlight ──
|
|
309
|
+
const isTired = (energy < 20 || hunger < 30) && !isSleeping;
|
|
310
|
+
const eyeOpen = isSleeping ? 0.05 : (isTired ? 0.4 : 1.0) - blinkFade;
|
|
311
|
+
const ex1 = bdx - lookX * 0.08 + 0.11, ey1 = bdy - lookY * 0.05 + 0.02;
|
|
312
|
+
const ex2 = bdx - lookX * 0.08 - 0.11, ey2 = bdy - lookY * 0.05 + 0.02;
|
|
313
|
+
|
|
314
|
+
if (isSleeping || currentState === "singing") {
|
|
315
|
+
// Closed eyes — horizontal lines
|
|
316
|
+
if (isSleeping) {
|
|
317
|
+
if ((Math.abs(ey1) < 0.012 && Math.abs(ex1) < 0.06) || (Math.abs(ey2) < 0.012 && Math.abs(ex2) < 0.06)) { r = 40; g = 30; b = 50; }
|
|
318
|
+
} else {
|
|
319
|
+
// Happy squint arcs
|
|
320
|
+
if ((Math.abs(ey1 + ex1 * ex1 * 12) < 0.018 && Math.abs(ex1) < 0.07 && ey1 > -ex1 * ex1 * 12) ||
|
|
321
|
+
(Math.abs(ey2 + ex2 * ex2 * 12) < 0.018 && Math.abs(ex2) < 0.07 && ey2 > -ex2 * ex2 * 12)) { r = 40; g = 30; b = 50; }
|
|
322
|
+
}
|
|
339
323
|
} else {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
324
|
+
const eDist1 = ex1 * ex1 + (ey1 * ey1) / (eyeOpen * eyeOpen + 0.001);
|
|
325
|
+
const eDist2 = ex2 * ex2 + (ey2 * ey2) / (eyeOpen * eyeOpen + 0.001);
|
|
326
|
+
|
|
327
|
+
// Layer 1: White sclera (outermost)
|
|
328
|
+
if (eDist1 < 0.009 || eDist2 < 0.009) {
|
|
329
|
+
r = 250; g = 250; b = 255;
|
|
330
|
+
|
|
331
|
+
// Layer 2: Colored iris
|
|
332
|
+
if (eDist1 < 0.005 || eDist2 < 0.005) {
|
|
333
|
+
r = 40; g = 130; b = 90; // teal-green iris
|
|
334
|
+
// Lower iris lighter
|
|
335
|
+
if (ey1 > 0.01 || ey2 > 0.01) { r = 60; g = 170; b = 110; }
|
|
336
|
+
|
|
337
|
+
// Layer 3: Dark pupil
|
|
338
|
+
if (eDist1 < 0.002 || eDist2 < 0.002) {
|
|
339
|
+
r = 10; g = 10; b = 15;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Big highlight (upper-left) — spans ~2 chars
|
|
344
|
+
if ((ex1 + 0.02) ** 2 + (ey1 + 0.02) ** 2 < 0.0015 || (ex2 + 0.02) ** 2 + (ey2 + 0.02) ** 2 < 0.0015) {
|
|
345
|
+
if (!isTired) { r = 255; g = 255; b = 255; }
|
|
346
|
+
}
|
|
347
|
+
// Small secondary highlight (lower-right)
|
|
348
|
+
if ((ex1 - 0.02) ** 2 + (ey1 - 0.02) ** 2 < 0.0006 || (ex2 - 0.02) ** 2 + (ey2 - 0.02) ** 2 < 0.0006) {
|
|
349
|
+
if (!isTired) { r = 230; g = 240; b = 255; }
|
|
350
|
+
}
|
|
351
|
+
}
|
|
343
352
|
}
|
|
344
|
-
|
|
345
|
-
|
|
353
|
+
|
|
354
|
+
// ── Nose: small dark oval ──
|
|
355
|
+
const nnx = bdx - lookX * 0.06, nny = bdy - lookY * 0.05 - 0.03;
|
|
356
|
+
if (nnx * nnx * 1.2 + nny * nny < 0.001 && !isSleeping) { r = 40; g = 30; b = 40; }
|
|
357
|
+
|
|
358
|
+
// ── Mouth: clear smile arc ──
|
|
359
|
+
if (!isSleeping && !hasBall) {
|
|
360
|
+
const mx = bdx - lookX * 0.06, my = bdy - lookY * 0.05 - 0.07;
|
|
361
|
+
// Smile curves
|
|
362
|
+
if ((Math.abs(my - (mx - 0.03) ** 2 * 15 + 0.012) < 0.013 && mx > 0 && mx < 0.06) ||
|
|
363
|
+
(Math.abs(my - (mx + 0.03) ** 2 * 15 + 0.012) < 0.013 && mx < 0 && mx > -0.06)) {
|
|
364
|
+
r = 50; g = 30; b = 40;
|
|
365
|
+
}
|
|
366
|
+
// Open mouth when excited/talking
|
|
367
|
+
if (currentState === "excited" || currentState === "singing" || currentState === "dance" || speechTimer > 0 || isTalking) {
|
|
368
|
+
const mouthOpen = (speechTimer > 0 || currentState === "singing" || isTalking)
|
|
369
|
+
? (isTalking ? talkAudioLevel * 0.04 + 0.008 : Math.abs(Math.sin(time * 12)) * 0.03)
|
|
370
|
+
: 0.02;
|
|
371
|
+
if (mx * mx + (my + 0.012) ** 2 < mouthOpen && my < -0.01) {
|
|
372
|
+
r = 230; g = 70; b = 90;
|
|
373
|
+
if (my < -0.03) { r = 255; g = 110; b = 130; }
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
} else {
|
|
378
|
+
if (hitObj.id === "earL" || hitObj.id === "earR") {
|
|
379
|
+
if (hitU > -0.3 && hitU < 0.3 && hitV > -0.5 && hitV < 0.5) { r = 255; g = 130; b = 160; }
|
|
346
380
|
}
|
|
381
|
+
}
|
|
382
|
+
// Dark outline — but NOT on the face area (preserves feature contrast)
|
|
383
|
+
if (hitNz < 0.25 && !isOnFace) {
|
|
384
|
+
r = Math.floor(r * 0.6); g = Math.floor(g * 0.6); b = Math.floor(b * 0.6);
|
|
385
|
+
}
|
|
347
386
|
} else if (hitObj.mat === 2) {
|
|
348
387
|
r = Math.max(0, th.r - 20); g = Math.max(0, th.g - 15); b = Math.max(0, th.b - 10);
|
|
349
388
|
if (hitNy > 0.5) { r = 255; g = 180; b = 190; }
|
|
@@ -457,45 +496,121 @@ function getPixel(px: number, py: number, objects: RenderObj[], skyColors: Retur
|
|
|
457
496
|
const directHit = getObjHit(px, py, objects);
|
|
458
497
|
if (directHit.hitObj) return shadeObject(directHit, px, py, objects);
|
|
459
498
|
|
|
499
|
+
const w = (skyColors as any).weather as Weather | undefined;
|
|
500
|
+
const tod = (skyColors as any).timeOfDay as TimeOfDay | undefined;
|
|
501
|
+
|
|
460
502
|
const grad = Math.max(0, (1.0 + py) / 2.0);
|
|
461
503
|
let bgR = Math.floor(skyColors.rTop * (1 - grad) + skyColors.rBot * grad);
|
|
462
504
|
let bgG = Math.floor(skyColors.gTop * (1 - grad) + skyColors.gBot * grad);
|
|
463
505
|
let bgB = Math.floor(skyColors.bTop * (1 - grad) + skyColors.bBot * grad);
|
|
464
506
|
|
|
465
|
-
//
|
|
507
|
+
// 5. SNOW: soft glow/haziness across whole sky
|
|
508
|
+
if (w === "snow") {
|
|
509
|
+
bgR = Math.min(255, bgR + 30);
|
|
510
|
+
bgG = Math.min(255, bgG + 30);
|
|
511
|
+
bgB = Math.min(255, bgB + 40);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// 3. STARS & MOON
|
|
466
515
|
if (skyColors.isNight) {
|
|
467
|
-
const
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
516
|
+
const moonDist = Math.sqrt((px - 0.4) ** 2 + (py + 0.4) ** 2);
|
|
517
|
+
if (moonDist < 0.15) {
|
|
518
|
+
const moonGlow = 1.0 - (moonDist / 0.15);
|
|
519
|
+
bgR = Math.min(255, bgR + moonGlow * 60);
|
|
520
|
+
bgG = Math.min(255, bgG + moonGlow * 60);
|
|
521
|
+
bgB = Math.min(255, bgB + moonGlow * 80);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
const starPattern = Math.sin(px * 150) * Math.cos(py * 150 + px * 40);
|
|
525
|
+
if (starPattern > 0.92) {
|
|
526
|
+
const twinkle = Math.sin(time * 3 + px * 30 + py * 40) * 0.5 + 0.5;
|
|
527
|
+
const starColorHash = Math.abs(Math.sin(px * 313 + py * 717));
|
|
528
|
+
let sr = 255, sg = 255, sb = 255;
|
|
529
|
+
if (starColorHash < 0.3) { sr = 180; sg = 200; sb = 255; }
|
|
530
|
+
else if (starColorHash < 0.6) { sr = 255; sg = 255; sb = 180; }
|
|
531
|
+
else if (starColorHash < 0.8) { sr = 255; sg = 180; sb = 150; }
|
|
532
|
+
|
|
533
|
+
const intensity = starPattern > 0.98 ? twinkle : twinkle * 0.4;
|
|
534
|
+
bgR = Math.min(255, bgR + sr * intensity);
|
|
535
|
+
bgG = Math.min(255, bgG + sg * intensity);
|
|
536
|
+
bgB = Math.min(255, bgB + sb * intensity);
|
|
537
|
+
}
|
|
471
538
|
}
|
|
472
539
|
|
|
473
|
-
//
|
|
474
|
-
|
|
540
|
+
// 4. SUNSET/DAWN (SUN DISK)
|
|
541
|
+
if (tod === "sunset" || tod === "dawn") {
|
|
542
|
+
const sunDist = Math.sqrt((px + 0.3) ** 2 + (py - 0.2) ** 2);
|
|
543
|
+
const halo = Math.max(0, 1.0 - sunDist * 2.0);
|
|
544
|
+
if (sunDist < 0.05) {
|
|
545
|
+
bgR = 255; bgG = 240; bgB = 200;
|
|
546
|
+
} else if (halo > 0) {
|
|
547
|
+
const hIntensity = Math.pow(halo, 2);
|
|
548
|
+
bgR = Math.min(255, bgR + hIntensity * 120);
|
|
549
|
+
bgG = Math.min(255, bgG + hIntensity * (tod === "sunset" ? 70 : 90));
|
|
550
|
+
bgB = Math.min(255, bgB + hIntensity * 40);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// 1. CLOUDS & 6. STORM CLOUDS
|
|
475
555
|
if (w === "cloudy" || w === "rain" || w === "storm" || w === "snow") {
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
556
|
+
let cloudDensity = w === "storm" ? 0.8 : w === "rain" ? 0.6 : w === "snow" ? 0.5 : 0.4;
|
|
557
|
+
|
|
558
|
+
// Denser in upper half, wispy at lower altitudes
|
|
559
|
+
cloudDensity *= Math.max(0, 1.0 - (py + 1.0) / 1.6);
|
|
560
|
+
|
|
561
|
+
const drift = time * 0.15;
|
|
562
|
+
const n1 = Math.sin((px + drift) * 6) * Math.cos(py * 8) * 0.5 + 0.5;
|
|
563
|
+
const n2 = Math.sin((px - drift * 0.5) * 14 + py * 10) * 0.5 + 0.5;
|
|
564
|
+
const n3 = Math.sin((px + drift * 2) * 25 - py * 20) * 0.5 + 0.5;
|
|
565
|
+
const noise = n1 * 0.5 + n2 * 0.3 + n3 * 0.2;
|
|
566
|
+
|
|
567
|
+
const shadowNoise = Math.sin((px + drift) * 6 + 0.3) * Math.cos((py - 0.08) * 8) * 0.5 + 0.5;
|
|
568
|
+
|
|
569
|
+
if (noise > 1.0 - cloudDensity) {
|
|
570
|
+
const blend = Math.min(1.0, (noise - (1.0 - cloudDensity)) * 4);
|
|
571
|
+
const isShadow = shadowNoise < noise - 0.1;
|
|
572
|
+
|
|
573
|
+
let cr = w === "storm" ? 60 : 200;
|
|
574
|
+
let cg = w === "storm" ? 65 : 205;
|
|
575
|
+
let cb = w === "storm" ? 75 : 215;
|
|
576
|
+
|
|
577
|
+
if (isShadow) {
|
|
578
|
+
cr *= 0.6; cg *= 0.6; cb *= 0.7;
|
|
579
|
+
}
|
|
580
|
+
|
|
482
581
|
bgR = Math.floor(bgR * (1 - blend) + cr * blend);
|
|
483
582
|
bgG = Math.floor(bgG * (1 - blend) + cg * blend);
|
|
484
583
|
bgB = Math.floor(bgB * (1 - blend) + cb * blend);
|
|
485
584
|
}
|
|
486
585
|
}
|
|
487
586
|
|
|
488
|
-
//
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
587
|
+
// 6. STORM LIGHTNING
|
|
588
|
+
if (w === "storm" && Math.sin(time * 47) > 0.99) {
|
|
589
|
+
bgR = Math.min(255, bgR + 180);
|
|
590
|
+
bgG = Math.min(255, bgG + 180);
|
|
591
|
+
bgB = Math.min(255, bgB + 200);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// 2. RAIN VISUAL
|
|
595
|
+
if (w === "rain" || w === "storm") {
|
|
596
|
+
if (Math.sin(px * 40 + py * 80 + time * 15) > 0.95) {
|
|
597
|
+
bgR = Math.min(255, bgR + 50);
|
|
598
|
+
bgG = Math.min(255, bgG + 50);
|
|
599
|
+
bgB = Math.min(255, bgB + 70);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// 7. FOG/MIST
|
|
604
|
+
if (w === "rain" || w === "snow") {
|
|
605
|
+
const fogDist = Math.max(0, py);
|
|
606
|
+
if (fogDist > 0) {
|
|
607
|
+
const fogBlend = Math.min(1.0, (fogDist / 0.6) * 0.5);
|
|
608
|
+
const fr = w === "snow" ? 220 : 140;
|
|
609
|
+
const fg = w === "snow" ? 220 : 150;
|
|
610
|
+
const fb = w === "snow" ? 230 : 160;
|
|
611
|
+
bgR = Math.floor(bgR * (1 - fogBlend) + fr * fogBlend);
|
|
612
|
+
bgG = Math.floor(bgG * (1 - fogBlend) + fg * fogBlend);
|
|
613
|
+
bgB = Math.floor(bgB * (1 - fogBlend) + fb * fogBlend);
|
|
499
614
|
}
|
|
500
615
|
}
|
|
501
616
|
|