1 /** 2 * Game objects and logic 3 * 4 * License: 5 * This Source Code Form is subject to the terms of 6 * the Mozilla Public License, v. 2.0. If a copy of 7 * the MPL was not distributed with this file, You 8 * can obtain one at http://mozilla.org/MPL/2.0/. 9 * 10 * Authors: 11 * Vladimir Panteleev <vladimir@thecybershadow.net> 12 */ 13 14 module ae.demo.pewpew.objects; 15 16 import std.random; 17 import std.math; 18 19 import ae.utils.container.listnode; 20 import ae.utils.math; 21 import ae.utils.geometry; 22 import ae.utils.graphics.color; 23 import ae.utils.graphics.draw; 24 import ae.utils.graphics.image; 25 26 __gshared: 27 28 enum Plane 29 { 30 Logic, 31 BG0, 32 BG1, 33 BG2, 34 Ship, 35 PlasmaOrbs, 36 Enemies, 37 Particles, 38 Torpedoes, 39 Explosions, 40 Max 41 } 42 43 enum STAR_LAYERS = 3; 44 45 DListContainer!GameEntity[Plane.Max] planes; 46 bool initializing = true; 47 48 alias L16 COLOR; 49 //alias G8 COLOR; // less precise, but a bit faster 50 51 Image!COLOR canvas; 52 float cf(float x) { assert(canvas.w == canvas.h); return x*canvas.w; } 53 int ci(float x) { assert(canvas.w == canvas.h); return cast(int)(x*canvas.w); } 54 T cbound(T)(T x) { return bound(x, 0, canvas.w); } 55 auto BLACK = COLOR(0); 56 auto WHITE = COLOR(COLOR.ChannelType.max); 57 58 bool up, down, left, right, fire; 59 60 bool useAnalog; float analogX, analogY; 61 62 float frand () { return uniform!`[)`( 0.0f, 1.0f); } 63 float frands() { return uniform!`()`(-1.0f, 1.0f); } 64 65 T ssqr(T)(T x) { return sqr(x) * sign(x); } 66 float frands2() { return ssqr(frands()); } 67 68 mixin FixMath; 69 70 // ********************************************************* 71 72 class GameEntity 73 { 74 mixin DListLink; 75 Plane plane; 76 77 abstract void step(uint deltaTicks); 78 abstract void render(); 79 80 void add(Plane plane) 81 { 82 this.plane = plane; 83 planes[plane].pushBack(this); 84 } 85 86 void remove() 87 { 88 planes[plane].remove(this); 89 } 90 } 91 92 class Game : GameEntity 93 { 94 this() 95 { 96 add(Plane.Logic); 97 spawnParticles = new SpawnParticles(Plane.Particles); 98 foreach (layer; 0..STAR_LAYERS) 99 starFields[layer] = new StarField(cast(Plane)(Plane.BG0 + layer)); 100 torpedoParticles = new TorpedoParticles(Plane.Particles); 101 } 102 103 uint spawnTimer; 104 105 override void step(uint deltaTicks) 106 { 107 foreach (i; 0..deltaTicks) 108 { 109 auto z = frand(); 110 auto star = Star( 111 frand(), 0, 112 0.0001f + (1-z) * 0.00005f, 113 COLOR(fixfpart(tofix((1-z)*0.5f)))); 114 auto layer = cast(int)((1-z)*3); 115 starFields[layer].add(star); 116 } 117 if (!initializing && ship && !ship.dead && spawnTimer--==0) 118 { 119 new Thingy(); 120 spawnTimer = uniform(1500, 2500); 121 } 122 if (!initializing && planes[Plane.Ship].empty && planes[Plane.Enemies].empty && planes[Plane.PlasmaOrbs].empty && planes[Plane.Explosions].empty) 123 new Ship(); 124 } 125 126 override void render() {} 127 } 128 129 // ********************************************************* 130 131 class ParticleManager(Particle) : GameEntity 132 { 133 Particle* particles; 134 int particleCount; 135 136 void add(Particle particle) 137 { 138 if (particleCount == Particle.MAX) 139 return; 140 particles[particleCount++] = particle; 141 } 142 143 this(Plane plane) 144 { 145 particles = (new Particle[Particle.MAX]).ptr; 146 super.add(plane); 147 } 148 149 override void step(uint deltaTicks) 150 { 151 int i = 0; 152 while (i < particleCount) 153 with (particles[i]) 154 { 155 enum REMOVE = q{ particles[i] = particles[--particleCount]; }; 156 enum NEXT = q{ i++; }; 157 mixin(Particle.STEP); 158 } 159 } 160 161 override void render() 162 { 163 import std.parallelism; 164 foreach (ref particle; taskPool.parallel(particles[0..particleCount])) 165 with (particle) 166 { 167 mixin(Particle.RENDER); 168 } 169 } 170 } 171 172 /+ 173 class ParticleManager(Particle) : GameEntity 174 { 175 Particle[] particles; 176 177 void add(Particle particle) 178 { 179 particles ~= particle; 180 } 181 182 this(Plane plane) 183 { 184 super.add(plane); 185 } 186 187 override void step(uint deltaTicks) 188 { 189 for (int i=0; i<particles.length; i++) 190 with (particles[i]) 191 { 192 enum REMOVE = q{ particles = particles[0..i] ~ particles[i+1..$]; }; 193 enum NEXT = q{ }; 194 mixin(Particle.STEP); 195 } 196 } 197 198 override void render() 199 { 200 foreach (ref particle; particles) 201 with (particle) 202 { 203 mixin(Particle.RENDER); 204 } 205 } 206 } 207 +/ 208 209 struct Star 210 { 211 float x, y, vy; 212 COLOR color; 213 214 enum MAX = 1024*32; 215 216 enum STEP = 217 q{ 218 y += deltaTicks * vy; 219 if (y > 1f) 220 { mixin(REMOVE); } 221 else 222 { mixin(NEXT); } 223 }; 224 225 enum RENDER = 226 q{ 227 canvas.aaPutPixel(cf(x), cf(y), color); 228 }; 229 } 230 231 alias ParticleManager!Star StarField; 232 StarField[STAR_LAYERS] starFields; 233 234 // ********************************************************* 235 236 class GameObject : GameEntity 237 { 238 float x, y, vx=0, vy=0; 239 Shape!float[] shapes; /// shape coordinates are relative to x,y 240 bool dead; 241 242 enum DEATHBRAKES = 0.998f; 243 244 override void step(uint deltaTicks) 245 { 246 x += vx*deltaTicks; 247 y += vy*deltaTicks; 248 if (dead) 249 vx *= DEATHBRAKES, 250 vy *= DEATHBRAKES; 251 } 252 253 final void collideWith(Plane[] planeIndices...) 254 { 255 assert(!dead); 256 257 foreach (plane; planeIndices) 258 foreach (obj; planes[plane]) 259 { 260 auto enemy = cast(GameObject) obj; 261 if (enemy && !enemy.dead) 262 foreach (shape1; shapes) 263 { 264 if (shape1.kind == ShapeKind.none) continue; 265 shape1.translate(x, y); 266 foreach (shape2; enemy.shapes) 267 { 268 if (shape2.kind == ShapeKind.none) continue; 269 shape2.translate(enemy.x, enemy.y); 270 if (intersects(shape1, shape2)) 271 { 272 die(); 273 enemy.die(); 274 return; 275 } 276 } 277 } 278 } 279 } 280 281 void die() 282 { 283 remove(); 284 } 285 } 286 287 // ********************************************************* 288 289 class Ship : GameObject 290 { 291 float death = 0f, spawn = 0f; 292 bool spawning; 293 uint t; 294 295 enum SPAWN_START = 0.3f; 296 enum SPAWN_END = 0.3f; 297 298 this() 299 { 300 x = 0.5f; 301 y = 0.85f; 302 vx = vy = 0; 303 shapes ~= shape(rect(-0.040f, -0.020f, -0.028f, +0.040f)); // left wing 304 shapes ~= shape(rect(+0.040f, -0.020f, +0.028f, +0.040f)); // right wing 305 shapes ~= shape(rect(-0.008f, -0.040f, +0.008f, +0.030f)); // center hull 306 shapes ~= shape(rect(-0.030f, +0.020f, +0.030f, +0.024f)); // bridge 307 shapes ~= shape(circle(0, +0.020f, 0.020f)); // round section 308 add(Plane.Ship); 309 ship = this; 310 dead = spawning = true; 311 } 312 313 override void step(uint deltaTicks) 314 { 315 if (!dead) 316 { 317 const a = 0.000_001f; 318 const maxv = 0.000_500f; 319 320 if (useAnalog) 321 vx = analogX * maxv, 322 vy = analogY * maxv; 323 else 324 { 325 if (left) 326 vx = bound(vx-a*deltaTicks, -maxv, 0); 327 else 328 if (right) 329 vx = bound(vx+a*deltaTicks, 0, maxv); 330 else 331 vx = 0; 332 333 if (up) 334 vy = bound(vy-a*deltaTicks, -maxv, 0); 335 else 336 if (down) 337 vy = bound(vy+a*deltaTicks, 0, maxv); 338 else 339 vy = 0; 340 } 341 342 t += deltaTicks; 343 } 344 super.step(deltaTicks); 345 346 if (!dead) 347 { 348 if (x<0.05f || x>0.95f) vx = 0; 349 if (y<0.05f || y>0.95f) vy = 0; 350 x = bound(x, 0.05f, 0.95f); 351 y = bound(y, 0.05f, 0.95f); 352 353 static bool wasFiring; 354 //fire = t % 250 == 0; 355 if (fire && !wasFiring) 356 { 357 new Torpedo(-0.034f, -0.020f); 358 new Torpedo(+0.034f, -0.020f); 359 } 360 wasFiring = !!fire; 361 362 collideWith(Plane.Enemies, Plane.PlasmaOrbs); 363 } 364 else 365 if (spawning) 366 { 367 spawn += 0.0005f; 368 369 if (spawn < SPAWN_START+1f) 370 foreach (n; 0..5) 371 { 372 float px = frands()*0.050f; 373 spawnParticles.add(SpawnParticle( 374 x + px*1.7f + frands ()*0.010f, 375 spawnY() + frands2()*0.050f, 376 x + px, 377 )); 378 } 379 380 if (spawn >= SPAWN_START+1f+SPAWN_END) 381 spawning = dead = false; 382 } 383 else 384 { 385 death += (1f/2475f); 386 if (death > 1f) 387 remove(); 388 } 389 } 390 391 override void die() 392 { 393 new Explosion(this, 0.150f); 394 dead = true; 395 } 396 397 override void render() 398 { 399 enum Gray25 = COLOR.ChannelType.max / 4; 400 enum Gray75 = COLOR.ChannelType.max / 4 * 3; 401 402 void drawRect(float x0, float y0, float x1, float y1, COLOR color) 403 { 404 canvas.aaFillRect(cf(x+x0), cf(y+y0), cf(x+x1), cf(y+y1), color); 405 } 406 407 void drawRect2(Rect!float r) 408 { 409 r.sort(); 410 drawRect(r.x0 , r.y0 , r.x1 , r.y1 , COLOR(Gray25)); 411 drawRect(r.x0+0.002f, r.y0+0.002f, r.x1-0.002f, r.y1-0.002f, COLOR(Gray75)); 412 } 413 414 void drawCircle(Circle!float c, COLOR color) 415 { 416 canvas.softCircle(cf(x+c.x), cf(y+c.y), cf(c.r*0.7f), cf(c.r), color); 417 } 418 419 420 void warp(float x, float y, float r) 421 { 422 auto bgx0 = cbound(ci(x-r)); 423 auto bgy0 = cbound(ci(y-r)); 424 auto bgx1 = cbound(ci(x+r)); 425 auto bgy1 = cbound(ci(y+r)); 426 auto window = canvas.crop(bgx0, bgy0, bgx1, bgy1); 427 static Image!COLOR bg; 428 window.copy(bg); 429 auto cx = ci(x)-bgx0; 430 auto cy = ci(y)-bgy0; 431 procedural!((x, y) 432 { 433 int dx = x-cx; 434 int dy = y-cy; 435 int sx = x; 436 int sy = y; 437 float f = dist(dx, dy) / cx; 438 if (f < 1f && f > 0f) 439 { 440 float f2 = (1-f)*sqrt(sqrt(f)) + f*f; 441 assert(f2 < 1f); 442 sx = cx + cast(int)(dx / f * f2); 443 sy = cy + cast(int)(dy / f * f2); 444 } 445 return bg.safeGet(sx, sy, COLOR(0)); 446 })(window.w, window.h).blitTo(window); 447 } 448 449 if (spawning) 450 { 451 enum R = 0.15f; 452 warp(x, spawnY(), R * sqrt(sin(spawn/(SPAWN_START+1f+SPAWN_END)*PI))); 453 } 454 455 static Image!COLOR bg; 456 int bgx0, bgyS; 457 if (spawning) 458 { 459 bgx0 = ci(x-0.050f); 460 bgyS = ci(spawnY()); 461 canvas.crop(bgx0, bgyS, ci(x+0.050f), ci(y+0.050f)).copy(bg); 462 } 463 464 drawRect2 (shapes[0].rect); 465 drawRect2 (shapes[1].rect); 466 drawRect2 (shapes[2].rect); 467 drawRect (shapes[3].rect.tupleof, COLOR(Gray25)); 468 drawCircle(shapes[4].circle , COLOR(Gray75)); 469 470 if (spawning) 471 bg.blitTo(canvas, bgx0, bgyS); 472 } 473 474 float spawnY() 475 { 476 return y-0.050f + (0.100f * bound(spawn-SPAWN_START, 0f, 1f)); 477 } 478 } 479 480 Ship ship; 481 482 struct SpawnParticle 483 { 484 float x0, y0, x1, t=0f; 485 486 enum MAX = 1024*32; 487 488 enum STEP = 489 q{ 490 t += 0.002f; 491 if (t > 1f) 492 { mixin(REMOVE); } 493 else 494 { mixin(NEXT); } 495 }; 496 497 enum RENDER = 498 q{ 499 float y1 = ship.spawnY(); 500 float tt0 = sqr(sqr(t)); 501 float tt1 = min(1, tt0+0.15f); 502 float lx0 = x0 + tt0*(x1-x0); 503 float ly0 = y0 + tt0*(y1-y0); 504 float lx1 = x0 + tt1*(x1-x0); 505 float ly1 = y0 + tt1*(y1-y0); 506 //canvas.aaPutPixel(cf(x), cf(y), WHITE, tofracBounded(tt)); 507 canvas.aaLine(cf(lx0), cf(ly0), cf(lx1), cf(ly1), WHITE, tofracBounded(sqr(tt0))); 508 }; 509 } 510 511 alias ParticleManager!SpawnParticle SpawnParticles; 512 SpawnParticles spawnParticles; 513 514 // ********************************************************* 515 516 class Torpedo : GameObject 517 { 518 this(float dx, float dy) 519 { 520 this.x = ship.x + dx; 521 this.y = ship.y + dy; 522 this.vx = ship.vx; 523 this.vy = ship.vy - 0.000_550f; 524 shapes ~= Shape!float(Point!float(0, 0)); 525 add(Plane.Torpedoes); 526 } 527 528 int t; 529 530 override void step(uint deltaTicks) 531 { 532 super.step(deltaTicks); 533 if (y < -0.25f || x < 0 || x > 1) 534 return remove(); 535 536 //if ((t+=deltaTicks) % 1 == 0) 537 torpedoParticles.add(TorpedoParticle( 538 x, y, 539 frands()*0.000_010f, 540 frand ()*0.000_100f + 0.000_200f)); 541 542 collideWith(Plane.Enemies, Plane.PlasmaOrbs); 543 } 544 545 override void die() 546 { 547 remove(); 548 foreach (n; 0..500) 549 { 550 auto a = uniform(0, TAU); 551 torpedoParticles.add(TorpedoParticle(x, y, 552 frand()*cos(a)*0.000_300f, 553 frand()*sin(a)*0.000_300f - 0.000_100f, 554 0.003f)); 555 } 556 } 557 558 override void render() 559 { 560 canvas.aaPutPixel(cf(x), cf(y), WHITE); 561 } 562 } 563 564 struct TorpedoParticle 565 { 566 float x, y, vx, vy, s = 0.001f, t = 0; 567 568 enum MAX = 1024*64; 569 570 enum STEP = 571 q{ 572 x += vx; 573 y += vy; 574 t += s; 575 if (t >= 1) 576 mixin(REMOVE); 577 else 578 mixin(NEXT); 579 }; 580 581 enum RENDER = 582 q{ 583 canvas.aaPutPixel(cf(x), cf(y), WHITE, tofracBounded(1-t)); 584 }; 585 } 586 587 alias ParticleManager!TorpedoParticle TorpedoParticles; 588 TorpedoParticles torpedoParticles; 589 590 // ********************************************************* 591 592 class Enemy : GameObject 593 { 594 } 595 596 class ThingyPart : Enemy 597 { 598 float death = 0f; 599 600 override void render() 601 { 602 float r1 = shapes[0].circle.r; 603 float r0 = r1*(2f/3f); 604 605 if (!dead) 606 canvas.softCircle(cf(x), cf(y), cf(r0), cf(r1), WHITE); 607 else 608 { 609 canvas.softCircle(cf(x), cf(y), cf(r0), cf(r1), COLOR(tofracBounded(1-death))); 610 canvas.softCircle(cf(x), cf(y), cf(r0*death), cf(r1*death), BLACK); 611 } 612 } 613 } 614 615 class Thingy : ThingyPart 616 { 617 float a, va; 618 ThingySatellite[] satellites; 619 int charge; // max is 2000 620 621 this() 622 { 623 this.x = uniform(0f, 1f); 624 this.y = -0.060f; 625 this.vx = frands()*0.0003f; 626 this.vy = 0.0002f+frand()*0.0003f; 627 shapes ~= shape(circle(0, 0, 0.030f)); 628 va = 0.002f * sign(frands()); 629 a = frand()*TAU; 630 charge = 0; 631 auto numSatellites = uniform!"[]"(2, 2+(ship.t / 10_000)); 632 633 //charge=int.min; x=0.25f;vx=0;vy=0.0001f; static int c=2; numSatellites=c++; 634 635 foreach (i; 0..numSatellites) 636 satellites ~= new ThingySatellite(); 637 638 add(Plane.Enemies); 639 } 640 641 override void step(uint deltaTicks) 642 { 643 for (int n=0; n<deltaTicks; n++) 644 { 645 if (x < 0.020f) 646 vx = max(vx, -vx); 647 if (x > 0.980f) 648 vx = min(vx, -vx); 649 super.step(1); 650 if (y > 1.060f) 651 { 652 foreach (s; satellites) 653 if (!s.dead) 654 s.remove(); 655 remove(); 656 return; 657 } 658 a += va; 659 660 if (!dead) 661 { 662 foreach (s; satellites) 663 if (!s.dead) 664 charge++; 665 666 while (charge >= 2000) 667 { 668 charge -= 2000; 669 if (ship && !ship.dead) 670 new PlasmaOrb(this); 671 } 672 } 673 else 674 { 675 death += 0.002f; 676 if (death > 1) 677 remove(); 678 } 679 } 680 681 uint level = 0; 682 uint lastLevelCount = 1; 683 void arrange(ThingySatellite[] satellites) 684 { 685 foreach (i, s; satellites) 686 { 687 auto sd = 0.040f + 0.025f*level + s.death*0.020f; // satellite distance 688 auto sa = a + TAU*i/satellites.length + level*(TAU/lastLevelCount/2); 689 s.x = x + sd*cos(sa); 690 s.y = y + sd*sin(sa); 691 } 692 level++; 693 lastLevelCount = cast(uint)satellites.length; 694 } 695 696 if (satellites.length <= 8) 697 arrange(satellites[ 0.. $]); 698 else 699 if (satellites.length <= 12) 700 { 701 arrange(satellites[ 0.. 6]); 702 arrange(satellites[ 6.. $]); 703 } 704 else 705 if (satellites.length <= 24) 706 { 707 arrange(satellites[ 0.. 8]); 708 arrange(satellites[ 8.. $]); 709 } 710 else 711 if (satellites.length <= 32) 712 { 713 arrange(satellites[ 0.. 8]); 714 arrange(satellites[ 8..16]); 715 arrange(satellites[16.. $]); 716 } 717 else 718 if (satellites.length <= 48) 719 { 720 arrange(satellites[ 0.. 8]); 721 arrange(satellites[ 8..24]); 722 arrange(satellites[24.. $]); 723 } 724 else 725 if (satellites.length <= 64) 726 { 727 arrange(satellites[ 0.. 8]); 728 arrange(satellites[ 8..24]); 729 arrange(satellites[24..40]); 730 arrange(satellites[40.. $]); 731 } 732 else 733 { 734 arrange(satellites[ 0.. 8]); 735 arrange(satellites[ 8..24]); 736 arrange(satellites[24..48]); 737 arrange(satellites[48.. $]); 738 } 739 } 740 741 override void die() 742 { 743 dead = true; 744 foreach (s; satellites) 745 s.dead = true; 746 new Explosion(this, 0.060f); 747 } 748 } 749 750 class ThingySatellite : ThingyPart 751 { 752 this() 753 { 754 shapes ~= shape(circle(0, 0, 0.014f)); 755 add(Plane.Enemies); 756 } 757 758 override void step(uint deltaTicks) 759 { 760 if (dead) 761 { 762 death += 0.002f; 763 if (death > 1) 764 remove(); 765 } 766 } 767 768 override void die() 769 { 770 dead = true; 771 } 772 } 773 774 class PlasmaOrb : Enemy 775 { 776 int t; 777 float death = 0f; 778 779 this(Enemy parent) 780 { 781 this.x = parent.x; 782 this.y = parent.y; 783 shapes ~= shape(circle(0, 0, 0.008f)); 784 t = 0; 785 vx = ship.x - parent.x; 786 vy = ship.y - parent.y; 787 float f = 0.0002f/dist(this.vx, this.vy); 788 vx *= f; 789 vy *= f; 790 add(Plane.PlasmaOrbs); 791 } 792 793 override void step(uint deltaTicks) 794 { 795 super.step(deltaTicks); 796 if (x<0 || x>1 || y<0 || y>1) 797 return remove(); 798 if (!dead) 799 { 800 t += deltaTicks; 801 shapes[0].circle.r = 0.008f+0.002f*sin(t/100f); 802 } 803 else 804 { 805 shapes[0].circle.r += 0.000_050f; 806 death += 0.005f; 807 if (death >= 1f) 808 remove(); 809 } 810 } 811 812 override void die() 813 { 814 dead = true; 815 } 816 817 override void render() 818 { 819 auto r = shapes[0].circle.r; 820 auto brightness = 0.75f+0.25f*(-sin(t/100f)); 821 if (!dead) 822 canvas.softCircle(cf(x), cf(y), cf(r-0.003f), cf(r), COLOR(tofracBounded(brightness))); 823 else 824 { 825 brightness *= 1-(death/2); 826 canvas.softRing(cf(x), cf(y), cf(death*r), cf(average(r, death*r)), cf(r), COLOR(tofracBounded(brightness))); 827 } 828 } 829 } 830 831 // ********************************************************* 832 833 class Explosion : GameObject 834 { 835 float size, maxt; 836 int t; 837 838 this(GameObject source, float size) 839 { 840 this.x = source.x; 841 this.y = source.y; 842 this.vx = source.vx; 843 this.vy = source.vy; 844 this.size = size; 845 this.t = 0; 846 this.maxt = size*16500; 847 this.dead = true; 848 add(Plane.Explosions); 849 } 850 851 override void step(uint deltaTicks) 852 { 853 t += deltaTicks; 854 super.step(deltaTicks); 855 856 if (t>maxt) 857 remove; 858 else 859 if (frand() < size) 860 { 861 float tf = t/maxt; // time factor 862 float ex = x+tf*size*frands(); 863 float ey = y+tf*size*frands(); 864 float ed = dist(x-ex, y-ey); 865 if (ed>size) return; 866 float r = 867 frand() * // random factor 868 (1-ed/size); // distance factor 869 new Splode(ex, ey, size/3*r); 870 } 871 } 872 873 override void die() { assert(0); } 874 875 override void render() 876 { 877 } 878 } 879 880 class Splode : GameEntity 881 { 882 float x, y, r, cr; 883 bool growing; 884 885 this(float x, float y, float r) 886 { 887 this.x = x; 888 this.y = y; 889 this.r = r; 890 this.cr = 0; 891 this.growing = true; 892 add(Plane.Explosions); 893 } 894 895 override void step(uint deltaTicks) 896 { 897 const ra = 0.000_020f; 898 if (growing) 899 { 900 cr += ra; 901 if (cr>=r) 902 growing = false; 903 } 904 else 905 { 906 cr -= ra; 907 if (cr<=0) 908 { 909 remove(); 910 return; 911 } 912 } 913 } 914 915 override void render() 916 { 917 //std.stdio.writeln([x, y, cr]); 918 canvas.softCircle(cf(x), cf(y), max(0f, cf(cr)-1.5f), cf(cr), COLOR(tofracBounded(cr/r))); 919 } 920 }