aardio 文档

aardio 范例: 种出一颗星球 · A Planet from a Seed

import win.ui;
import gdip.path;
import gdip.pathGradientBrush;
import gdip.font;

/*DSG{{*/
var winform = win.form(text="种出一颗星球 · A Planet from a Seed";right=979;bottom=649;bgcolor=0x100B07)
winform.add(
    canvas={cls="canvas";left=0;top=0;right=980;bottom=650;bgcolor=0x100B07;anchors={left=1;top=1;right=1;bottom=1};z=1}
)
/*}}*/

/*
    《种出一颗星球》— 纯 aardio canvas / GDI+ 绘制,无外部素材。
    一个周期 15 秒:种子呼吸 → 花瓣展开 → 微型宇宙运转 → 折叠归位。
    空格暂停/继续,R 重新开始,Esc 关闭窗口。
*/
var cycleMs = 15000;
var tau = math.pi * 2;
var elapsedMs = 0;
var paused = false;

var smooth = function(a,b,x){
    var q = math.clamp((x-a)/(b-a),0,1);
    return q*q*(3-2*q);
};
var withAlpha = function(rgb,alpha){
    return math.floor(math.clamp(alpha,0,255))*0x1000000 + (rgb & 0xFFFFFF);
};

// 状态只由周期内的时间决定;首帧与末帧是同一个状态。
var sceneState = function(ms){
    var t = (ms % cycleMs) / 1000;
    var open = smooth(1.15,4.30,t) * (1-smooth(10.35,13.55,t));
    var planet = smooth(1.95,4.45,t) * (1-smooth(10.05,12.90,t));
    var camera = 1 + 0.20*smooth(3.40,8.50,t)*(1-smooth(10.30,13.85,t));
    var delays = [0,0.20,0.10,0.30];
    var petals = [];
    for(i=1,4){
        var d = delays[i];
        petals[i] = smooth(1.18+d,4.03+d,t) * (1-smooth(10.35+0.30-d,13.20+0.30-d,t));
    }
    return {
        t=t; open=open; planet=planet; camera=camera; petals=petals;
        bob=3.2*math.sin(tau*t/3);
        breath=1+0.018*math.sin(tau*t/3)*(1-open);
        shimmer=0.5+0.5*math.sin(tau*t/3);
        orbit1=tau*t/5-1.0;
        orbit2=tau*t/7.5+1.5;
        gear=t*72;
    };
};

// 固定形状和绘图资源只创建一次,不在每帧创建和销毁。
var shellPath = gdip.path();
// 相邻壳片共用同一条曲线接缝;四片旋转后仍能拼成完整球面。
shellPath.addBezier(0,0,-11,-4,-33,-24,-34,-34);
shellPath.addBezier(-34,-34,-19,-53,19,-53,34,-34);
shellPath.addBezier(34,-34,24,-33,4,-11,0,0);
shellPath.closeFigure();

var seedPath = gdip.path(); seedPath.addCircle(0,0,48.5);
var seedBrush = gdip.pathGradientBrush(seedPath);
seedBrush.setCenterPoint(-14,-18);
seedBrush.setCenterColor(0xFFB19B68);
seedBrush.setSurroundColors([0xFF29251F]);

// 闭合时给所有壳片一套共同的高光和暗面,让它们读作同一颗球。
var seedLight = gdip.pathGradientBrush(seedPath);
seedLight.setCenterPoint(-21,-25);
seedLight.setCenterColor(0x60FFF0B8);
seedLight.setSurroundColors([0x00FFF0B8]);
var seedShadow = gdip.pathGradientBrush(seedPath);
seedShadow.setCenterPoint(23,25);
seedShadow.setCenterColor(0x78040A0F);
seedShadow.setSurroundColors([0x00040A0F]);

// 同一球面光源反变换到每片壳的局部坐标;闭合时高光连续,展开后随壳片翻转。
var shellMaterials=[];
for(i=1,4){
    var a=(i-1)*math.pi/2;
    var lx=-18*math.cos(a)-21*math.sin(a);
    var ly=18*math.sin(a)-21*math.cos(a);
    var material=gdip.pathGradientBrush(seedPath);
    material.setCenterPoint(lx,ly);
    material.setCenterColor(0xFFD0B77F);
    material.setSurroundColors([0xFF302A22]);
    material.setFocusScales(0.05,0.05);
    shellMaterials[i]=material;
}

var planetPath = gdip.path(); planetPath.addCircle(0,0,61);
var planetBrush = gdip.pathGradientBrush(planetPath);
planetBrush.setCenterPoint(-20,-25);
planetBrush.setCenterColor(0xFF52929A);
planetBrush.setSurroundColors([0xFF091D2B]);
planetBrush.setFocusScales(0.08,0.08);

var land1 = gdip.path();
land1.addClosedCurve2(0.23,[-49,-19,-39,-34,-27,-43,-15,-40,-8,-31,2,-34,10,-23,5,-14,-8,-13,-13,-4,-25,-7,-30,1,-42,-5]);
var land2 = gdip.path();
land2.addClosedCurve2(0.22,[16,2,28,5,39,13,34,24,27,29,24,42,14,38,8,27,4,18,9,12]);
var land3 = gdip.path();
land3.addClosedCurve2(0.24,[33,-39,44,-34,50,-24,44,-17,34,-19,29,-28]);

// 12 齿齿轮;大小和转角由其所属的铰链驱动。
var gearPath = gdip.path();
var gearPoints = [];
for(i=0,11){
    for(j=0,3){
        var a = tau*(i+j/4)/12;
        var radius = 16;
        if(j==1 || j==2) radius = 20;
        table.push(gearPoints,radius*math.cos(a),radius*math.sin(a));
    }
}
gearPath.addPolygon(gearPoints);
// 齿数 12:7,齿距相同,啮合时角速度按齿数反比。
var gear7Path = gdip.path();
var gear7Points = [];
for(i=0,6){
    for(j=0,3){
        var a = tau*(i+j/4)/7;
        var radius = 16;
        if(j==1 || j==2) radius = 20;
        table.push(gear7Points,radius*math.cos(a),radius*math.sin(a));
    }
}
gear7Path.addPolygon(gear7Points);

var darkBrush = gdip.solidBrush(0xFF1B2022);
var gearBrush = gdip.solidBrush(0xFF796641);
var goldBrush = gdip.solidBrush(0xFFBA9E68);
var highlightBrush = gdip.solidBrush(0xFFE4C98D);
var blueBrush = gdip.solidBrush(0xFF70D6E1);
var landBrush = gdip.solidBrush(0xFF85866A);
var landShade = gdip.solidBrush(0xFF687764);
var inkBrush = gdip.solidBrush(0xFF0B1820);
var mainPen = gdip.pen(0xFFB89C67,1);
var thinPen = gdip.pen(0xFFB89C67,0.8);
var heavyPen = gdip.pen(0xFF3D352A,3);
var bluePen = gdip.pen(0xFF6AC9D8,1);
var titleFont = gdip.font("Microsoft YaHei UI",20,0);
var smallFont = gdip.font("Segoe UI",9,0);
var labelFont = gdip.font("Segoe UI",10,0);

// 一张小型透明光晕精灵:缩放使用,不重复构造渐变。
var glowBitmap = gdip.bitmap(240,240,0xE200B);
var glowGraphics = gdip.graphics(glowBitmap);
glowGraphics.clear(0x00000000);
var glowPath = gdip.path(); glowPath.addCircle(120,120,118);
var glowBrush = gdip.pathGradientBrush(glowPath);
glowBrush.setCenterColor(0x5648BFCB);
glowBrush.setSurroundColors([0x0048BFCB]);
glowGraphics.fillPath(glowBrush,glowPath);
glowBrush.delete(); glowPath.delete(); glowGraphics.delete();

var backgroundBitmap;
var backgroundW,backgroundH,backgroundDpiX,backgroundDpiY = 0,0,0,0;

// 背景只在客户区大小或 DPI 改变时重建。
var makeBackground = function(w,h,dpiX,dpiY){
    var bmp = gdip.bitmap(w,h,0xE200B);
    var g = gdip.graphics(bmp);
    g.clear(0xFF070C13);
    g.smoothingMode = 4;
    var state = g.save();
    g.scale(dpiX,dpiY);
    var lw,lh = w/dpiX,h/dpiY;
    var backPath = gdip.path();
    backPath.addEllipse(-lw*0.23,-lh*0.33,lw*1.46,lh*1.65);
    var backGradient = gdip.pathGradientBrush(backPath);
    backGradient.setCenterPoint(lw*0.50,lh*0.47);
    backGradient.setCenterColor(0xFF172934);
    backGradient.setSurroundColors([0xFF060A10]);
    g.fillPath(backGradient,backPath);
    backGradient.delete(); backPath.delete();

    // 稀疏的固定星点和边角刻线,不参与逐帧动画。
    g.compositingQuality = 2;
    var pen = gdip.pen(0x39475B61,0.7);
    var dot = gdip.solidBrush(0x546D898A);
    if(lw>220 && lh>180){
        for(i=1,46){
            var x = 30 + ((i*181+31) % (lw-60));
            var y = 44 + ((i*127+29) % (lh-88));
            var dx = (x-lw*0.5)/(lw*0.5);
            var dy = (y-lh*0.48)/(lh*0.5);
            if(dx*dx+dy*dy>0.24){
                var rr = 0.55;
                if(i%9==0) rr=1.0;
                g.fillCircle(dot,x,y,rr);
                if(i%15==0){
                    g.drawLine(pen,x-3.0,y,x+3.0,y);
                    g.drawLine(pen,x,y-3.0,x,y+3.0);
                }
            }
        }
    }
    pen.color = 0x38667776;
    g.drawLine(pen,24,24,45,24); g.drawLine(pen,24,24,24,45);
    g.drawLine(pen,lw-24,24,lw-45,24); g.drawLine(pen,lw-24,24,lw-24,45);
    g.drawLine(pen,24,lh-24,45,lh-24); g.drawLine(pen,24,lh-24,24,lh-45);
    g.drawLine(pen,lw-24,lh-24,lw-45,lh-24); g.drawLine(pen,lw-24,lh-24,lw-24,lh-45);
    if(lw>500 && lh>360){
        dot.color = 0xFFBBAA83;
        g.drawString("种出一颗星球",titleFont,39,34,dot);
        dot.color = 0xFF718181;
        g.drawString("A  PLANET  FROM  A  SEED",smallFont,40,66,dot);
        pen.color = 0x557D8C87;
        g.drawLine(pen,40,88,118,88);
        dot.color = 0xFF71817D;
        g.drawString("MECHANICAL  BOTANICA    /    01",smallFont,39,lh-51,dot);
        g.drawString("15 SEC   ·   LOOP",labelFont,lw-153,lh-51,dot);
    }
    pen.delete(); dot.delete();
    g.restore(state); g.delete();
    return bmp;
};

// 圆环在种子内部缩至零;四个铰链始终落在各自的径向上。
var drawFrameRing = function(g,s){
    var e = s.open;
    var r = 132*e;
    if(r<3) return;
    var opacity = smooth(0.06,0.45,e);
    heavyPen.width=5.8; heavyPen.color=withAlpha(0x4B3D2A,220*opacity);
    mainPen.width=2.0; mainPen.color=withAlpha(0xC7A86B,215*opacity);
    thinPen.width=0.75; thinPen.color=withAlpha(0xE5CA8F,180*opacity);
    for(i=0,3){
        g.drawArc(heavyPen,-r,-r,2*r,2*r,i*90+7,76);
        g.drawArc(mainPen,-r,-r,2*r,2*r,i*90+7,76);
        if(r>11) g.drawArc(thinPen,-r+5,-r+5,2*(r-5),2*(r-5),i*90+14,62);
    }
    if(r>20){
        for(i=0,35){
            var a=tau*i/36;
            var inner=r-3.5;
            if(i%3==0) inner=r-7.0;
            g.drawLine(thinPen,inner*math.cos(a),inner*math.sin(a),r*math.cos(a),r*math.sin(a));
        }
    }
};

var drawArms = function(g,s){
    for(i=1,4){
        var r = 132*s.petals[i];
        if(r>42){
            var a=(i-1)*math.pi/2;
            var ux,uy=math.sin(a),-math.cos(a);
            heavyPen.width=6; heavyPen.color=0xFF3C3429;
            mainPen.width=2.4; mainPen.color=0xFF90774A;
            thinPen.width=0.75; thinPen.color=0xFFDDC082;
            g.drawLine(heavyPen,ux*38,uy*38,ux*r,uy*r);
            g.drawLine(mainPen,ux*38,uy*38,ux*r,uy*r);
            g.drawLine(thinPen,ux*45-uy*1.4,uy*45+ux*1.4,ux*(r-2)-uy*1.4,uy*(r-2)+ux*1.4);
            g.fillCircle(darkBrush,ux*r,uy*r,5.3);
            g.fillCircle(goldBrush,ux*r,uy*r,3.8);
            g.fillCircle(inkBrush,ux*r,uy*r,1.3);
        }
    }
};

var drawGear = function(g,x,y,scale,angle,shape){
    if(scale<0.01) return;
    shape = shape : gearPath;
    var st = g.save();
    g.translate(x,y); g.rotate(angle); g.scale(scale,scale);
    g.fillPath(darkBrush,shape);
    var shadow = g.save(); g.translate(0,-1.5);
    g.fillPath(gearBrush,shape);
    mainPen.color=0xFFBDA36B; mainPen.width=1.0;
    g.drawPath(mainPen,shape);
    g.fillCircle(inkBrush,0,0,9.3);
    thinPen.color=0xFFC2A56B; thinPen.width=0.9;
    g.drawCircle(thinPen,0,0,9.3);
    for(k=0,3){
        var a=tau*k/4;
        g.drawLine(thinPen,3*math.cos(a),3*math.sin(a),8*math.cos(a),8*math.sin(a));
    }
    g.fillCircle(goldBrush,0,0,2.6);
    g.restore(shadow); g.restore(st);
};

var drawGears = function(g,s){
    var size = smooth(0.18,0.80,s.open);
    if(size<0.01) return;
    var x = 42+90*s.open;
    var y = 44*s.open;
    heavyPen.color=0xFF3D3529; heavyPen.width=5;
    mainPen.color=0xFF8B764D; mainPen.width=1.6;
    var leftX,leftY=-x+10*size,y+10*size;
    g.drawLine(heavyPen,leftX,leftY,leftX+18,leftY-20); g.drawLine(heavyPen,x,y,x-18,y-20);
    g.drawLine(mainPen,leftX,leftY,leftX+18,leftY-20); g.drawLine(mainPen,x,y,x-18,y-20);
    drawGear(g,leftX,leftY,size,s.gear);
    drawGear(g,x,y,size,-s.gear);
    // 右侧的小齿轮与大齿轮反向联动。
    drawGear(g,x+29*size,y+2*size,size*(7/12),s.gear*(12/7)+6.4,gear7Path);
};

// 外壳是同一片路径:沿铰链伸出、旋转并压扁,最终成为轨道的金属护片。
var drawPetal = function(g,s,index){
    var e = s.petals[index];
    // 闭合时半透明壳面与底下的球体共享曲面光影;张开后显露完整金属厚度。
    var panelOpacity=0.25+0.75*smooth(0,0.30,e);
    var st = g.save();
    g.rotate((index-1)*90);
    g.translate(0,-132*e);
    var direction=1;
    if(index%2==0) direction=-1;
    g.rotate(direction*e*39);
    g.scale(1+0.25*e,1-0.30*e);
    var sh = g.save(); g.translate(1.4,2.5);
    darkBrush.color=withAlpha(0x1B2022,255*smooth(0.02,0.40,e));
    g.fillPath(darkBrush,shellPath); g.restore(sh);
    shellMaterials[index].setCenterColor(withAlpha(0xD0B77F,255*panelOpacity));
    shellMaterials[index].setSurroundColors([withAlpha(0x302A22,255*panelOpacity)]);
    g.fillPath(shellMaterials[index],shellPath);
    heavyPen.width=0.75+1.05*e; heavyPen.color=withAlpha(0x302A22,(125+130*e)*panelOpacity);
    g.drawPath(heavyPen,shellPath);
    thinPen.width=0.8; thinPen.color=withAlpha(0xD7BC83,(65+190*smooth(0,0.4,e))*panelOpacity);
    g.drawBezier(thinPen,-29,-33,-15,-46,15,-46,29,-33);
    thinPen.color=withAlpha(0x6B5B3C,(100+145*e)*panelOpacity);
    g.drawBezier(thinPen,-21,-29,-13,-25,-8,-15,-5,-8);
    g.drawBezier(thinPen,21,-29,13,-25,8,-15,5,-8);
    darkBrush.color=withAlpha(0x1B2022,255*panelOpacity);
    highlightBrush.color=withAlpha(0xE4C98D,255*panelOpacity);
    goldBrush.color=withAlpha(0xBA9E68,255*panelOpacity);
    inkBrush.color=withAlpha(0x0B1820,255*panelOpacity);
    // 靠球缘的铆钉被透视压扁,长轴沿球面切向。
    g.fillEllipse(darkBrush,-21.3,-35.3,4.6,2.7); g.fillEllipse(darkBrush,16.7,-35.3,4.6,2.7);
    g.fillCircle(highlightBrush,-19,-34,0.8); g.fillCircle(highlightBrush,19,-34,0.8);
    g.fillCircle(darkBrush,0,-37,3.7); g.fillCircle(goldBrush,0,-37,2.3);
    g.fillCircle(inkBrush,0,-37,0.85);
    darkBrush.color=0xFF1B2022; highlightBrush.color=0xFFE4C98D;
    goldBrush.color=0xFFBA9E68; inkBrush.color=0xFF0B1820;
    g.restore(st);
};

// 轨道以椭圆投影;sin(theta) 决定其在星球前面还是后面。
var orbitPoint = function(rx,ry,tilt,angle){
    var u,v=rx*math.cos(angle),ry*math.sin(angle);
    var c,ss=math.cos(tilt),math.sin(tilt);
    return u*c-v*ss,u*ss+v*c,math.sin(angle);
};
var drawOrbit = function(g,s,which,front){
    var visibility = smooth(0.12,0.62,s.open);
    if(visibility<=0) return;
    var rx,ry,tilt,color;
    if(which==1){
        rx=65+101*s.open; ry=19+37*s.open; tilt=-19; color=0xC8A96D;
    }
    else {
        rx=58+80*s.open; ry=31+40*s.open; tilt=51; color=0x65A7AE;
    }
    var st=g.save(); g.rotate(tilt);
    thinPen.width=0.9; thinPen.color=withAlpha(color,160*visibility);
    heavyPen.width=3.0; heavyPen.color=withAlpha(color,35*visibility);
    var start=180;
    if(front) start=0;
    g.drawArc(heavyPen,-rx,-ry,rx*2,ry*2,start,180);
    g.drawArc(thinPen,-rx,-ry,rx*2,ry*2,start,180);
    g.restore(st);
};
var drawSatellite = function(g,s,which,front){
    var visibility = smooth(0.20,0.72,s.open);
    if(visibility<=0) return;
    var rx,ry,tilt,angle;
    if(which==1){ rx=65+101*s.open; ry=19+37*s.open; tilt=-19*math.pi/180; angle=s.orbit1; }
    else { rx=58+80*s.open; ry=31+40*s.open; tilt=51*math.pi/180; angle=s.orbit2; }
    var x,y,depth = orbitPoint(rx,ry,tilt,angle);
    if((depth>=0) != front) return;
    var st=g.save(); g.translate(x,y);
    var size=0.45+0.55*visibility;
    g.scale(size,size);
    if(which==1){
        // 小型机械卫星:金属舱体与两侧的冷蓝面板随轨道一起运动。
        g.fillPolygon(inkBrush,[-16,-3.5,-9,-3.5,-9,3.5,-16,3.5]);
        g.fillPolygon(inkBrush,[9,-3.5,16,-3.5,16,3.5,9,3.5]);
        thinPen.color=0xFF6EADB5; thinPen.width=0.8;
        g.drawPolygon(thinPen,[-16,-3.5,-9,-3.5,-9,3.5,-16,3.5]);
        g.drawPolygon(thinPen,[9,-3.5,16,-3.5,16,3.5,9,3.5]);
        g.drawLine(thinPen,-12.5,-3.5,-12.5,3.5); g.drawLine(thinPen,12.5,-3.5,12.5,3.5);
    }
    g.fillCircle(darkBrush,1,1.5,8.3);
    g.fillCircle(gearBrush,0,0,7.4);
    mainPen.width=1; mainPen.color=0xFFE1C689;
    g.drawCircle(mainPen,0,0,7.4);
    g.fillCircle(inkBrush,0,0,4.2);
    blueBrush.color=0xFF70D6E1;
    g.fillCircle(blueBrush,-1.1,-1.2,2.1);
    thinPen.color=0xFFC8A86C; thinPen.width=0.8;
    g.drawLine(thinPen,-10,0,-7.3,0); g.drawLine(thinPen,7.3,0,10,0);
    g.restore(st);
};

var drawPlanet = function(g,s){
    var p = s.planet;
    if(p<0.001) return;
    var st = g.save();
    g.scale(p,p);
    g.drawImageRect(glowBitmap,-118,-118,236,236);
    g.fillPath(planetBrush,planetPath);
    var clip = g.save();
    g.setClipPath(planetPath);
    var terrain = g.save();
    g.translate(8*math.sin(tau*s.t/15),0);
    g.fillPath(landShade,land1); g.fillPath(landBrush,land2); g.fillPath(landShade,land3);
    thinPen.width=0.65; thinPen.color=0xB9B7C6A3;
    g.drawPath(thinPen,land1); g.drawPath(thinPen,land2); g.drawPath(thinPen,land3);
    blueBrush.color=0xFFA7E9E5;
    g.fillCircle(blueBrush,-28,-20,1.4); g.fillCircle(blueBrush,18,18,1.1);
    g.restore(terrain);
    var grid = g.save(); g.rotate(-17);
    thinPen.width=0.65; thinPen.color=0x5889C4CA;
    g.drawEllipse(thinPen,-63,-17,126,34);
    g.drawEllipse(thinPen,-55,-43,110,86);
    g.drawArc(thinPen,-25,-61,50,122,270,180);
    g.restore(grid);
    // 极淡的大气亮面,仍保留深色的夜半球。
    thinPen.width=1.0; thinPen.color=0x668FE0E0;
    g.drawArc(thinPen,-58,-58,116,116,200,105);
    g.restore(clip);
    heavyPen.color=0xFF163641; heavyPen.width=2.4;
    g.drawCircle(heavyPen,0,0,61);
    thinPen.color=0xFF80B7B5; thinPen.width=0.9;
    g.drawCircle(thinPen,0,0,60);
    g.restore(st);
};

var drawSeedFinish = function(g,s){
    var opacity=1-smooth(0,0.13,s.open);
    if(opacity<=0) return;
    seedLight.setCenterColor(withAlpha(0xFFF0B8,92*opacity));
    seedShadow.setCenterColor(withAlpha(0x040A0F,112*opacity));
    g.fillPath(seedLight,seedPath);
    g.fillPath(seedShadow,seedPath);
    heavyPen.width=0.9; heavyPen.color=withAlpha(0x16191B,175*opacity);
    g.drawCircle(heavyPen,0,0,48.3);
    thinPen.width=0.85; thinPen.color=withAlpha(0xF0D59B,185*opacity);
    g.drawArc(thinPen,-45,-45,90,90,200,86);
};

var drawSeam = function(g,s){
    var opacity = 1-smooth(0.06,0.48,s.open);
    if(opacity<=0) return;
    var st=g.save(); g.rotate(-16);
    // 先刻出暗槽,再在槽内画一线冷蓝光,沿球面轻轻弯曲。
    bluePen.width=3.5; bluePen.color=withAlpha(0x081722,190*opacity);
    g.drawBezier(bluePen,-45,-3,-17,8,17,8,45,-3);
    bluePen.width=5; bluePen.color=withAlpha(0x54D3E8,(28+13*s.shimmer)*opacity);
    g.drawBezier(bluePen,-45,-3,-17,8,17,8,45,-3);
    bluePen.width=1.15; bluePen.color=withAlpha(0x7DDBE9,(145+65*s.shimmer)*opacity);
    g.drawBezier(bluePen,-45,-3,-17,8,17,8,45,-3);
    bluePen.width=0.5; bluePen.color=withAlpha(0xD0FAF5,180*opacity);
    g.drawBezier(bluePen,-32,1,-12,7,12,7,32,1);
    g.restore(st);
};

var drawScene = function(g,s){
    // 背层:种子底壳、扩张的机械结构、后半轨道。
    var shellBase = 1-smooth(0.02,0.82,s.open);
    if(shellBase>0.001){
        var st=g.save(); g.scale(shellBase,shellBase); g.fillPath(seedBrush,seedPath); g.restore(st);
    }
    drawFrameRing(g,s);
    drawArms(g,s);
    drawGears(g,s);
    drawPetal(g,s,1); drawPetal(g,s,3);
    drawOrbit(g,s,1,false); drawOrbit(g,s,2,false);
    drawSatellite(g,s,1,false); drawSatellite(g,s,2,false);

    drawPlanet(g,s);

    // 前半轨道和卫星后绘制,形成可靠的前后遮挡关系。
    drawOrbit(g,s,1,true); drawOrbit(g,s,2,true);
    drawSatellite(g,s,1,true); drawSatellite(g,s,2,true);
    drawPetal(g,s,2); drawPetal(g,s,4);
    drawSeedFinish(g,s);
    drawSeam(g,s);
};

// drawFrame 也可用于离屏渲染;所有场景几何均使用 96-DPI 逻辑单位。
var drawFrame = function(g,w,h,dpiX,dpiY,ms){
    if(w<=0 || h<=0) return;
    if(!backgroundBitmap || w!=backgroundW || h!=backgroundH || dpiX!=backgroundDpiX || dpiY!=backgroundDpiY){
        if(backgroundBitmap) backgroundBitmap.delete();
        backgroundBitmap=makeBackground(w,h,dpiX,dpiY);
        backgroundW,backgroundH,backgroundDpiX,backgroundDpiY=w,h,dpiX,dpiY;
    }
    var saved=g.save();
    g.compositingMode=1; // 不透明背景的快速拷贝
    g.drawImage(backgroundBitmap,0,0);
    g.compositingMode=0;
    g.smoothingMode=4;
    var s=sceneState(ms);
    var lw,lh=w/dpiX,h/dpiY;
    var fit=math.min(lw/900,lh/600);
    g.scale(dpiX,dpiY);
    g.translate(lw*0.50,lh*0.48+s.bob*fit);
    var seedFraming=1+0.12*(1-smooth(0,0.70,s.open));
    var zoom=fit*s.camera*s.breath*seedFraming;
    g.scale(zoom,zoom);
    drawScene(g,s);
    g.restore(saved);
};

winform.canvas.onDrawForeground = function(g,rc){
    var dpiX,dpiY=winform.dpiScale(1,1);
    drawFrame(g,rc.width,rc.height,dpiX,dpiY,elapsedMs);
};
winform.canvas.onAnimation = function(previous,beginning,change,timestamp,duration,deltaMs){
    // 画面隐藏/最小化时不把长时间的暂停补进当前一帧。
    if(!paused && deltaMs<1000) elapsedMs += deltaMs;
    return true;
};
winform.canvas.dlgCode=4;
winform.canvas.onKeyDown = function(vk){
    if(vk==0x20) paused=!paused; // Space
    elseif(vk==0x52) elapsedMs=0; // R
    elseif(vk==0x1B) { winform.close(); return; } // Esc
    winform.canvas.redraw();
};

winform.onDestroy = function(){
    if(backgroundBitmap) backgroundBitmap.delete();
    for(i=1;#shellMaterials) shellMaterials[i].delete();
    var resources=[shellPath,seedPath,seedBrush,seedLight,seedShadow,planetPath,planetBrush,
        land1,land2,land3,gearPath,gear7Path,darkBrush,gearBrush,goldBrush,highlightBrush,blueBrush,
        landBrush,landShade,inkBrush,mainPen,thinPen,heavyPen,bluePen,titleFont,smallFont,labelFont,glowBitmap];
    for(i=1;#resources) resources[i].delete();
};

winform.show();
winform.canvas.setFocus();
winform.canvas.startAnimation(16);
win.loopMessage();

Markdown 格式