d4aa588da0
Replace the external StreamElements feed with NATIVE YouTube events per the creator's 2026-09-23 question. Chat parity half: - YouTubeChatService decodes ALL SIX liveChat/messages event types into ChatMessage.Kind (superChat/superSticker/newSponsor/membershipGifting/ giftMembershipReceived/memberMilestoneChat) — the four formerly-empty overlay rows are real now — and re-arms its one-shot poll on the server's pollingIntervalMillis (streamList connection semantics; clamp 1000-6000ms, maxResults=2000). ParsePage internal static seam + ChatPage record for deterministic tests; optional HttpClient ctor seam kept; InjectSimulatedMessage (TASK 41) preserved. Alerts half (creator rulings: one celebration zone, six UNIQUE animations, no menus/polls, sub mention = chat row only, NO viewer count): - New SourceType.AlertBox 'Stream Alerts' (one per layout, CanAddAlerts gate mirroring chat; idle = transparent). - AlertRenderer: six distinct branded animations (SuperChat slide-up/shine/ count-up, SuperSticker scale-pop, NewMember drop-in/flash, MemberGift slide-left/ chip-fan, GiftReceived confetti, MemberMilestone rise/growth-bar), every card drawing the 'made with LlamaCasty!' brand line. - AlertOverlayLayer: true component (Commit-G pattern) — queue (cap 10, drop tail never stall), 33ms UI ticker, cache-first RenderFrame + UpdatePreview, Advance(double) as the deterministic test clock; ChatEventKind.None rows never enqueue. - Wired: resolver RenderAlertBox, _alertLayer ctor + dispose, LoadLayout previews, Add menu item (Controls/LeftPanel.xaml), TestSessionViewModel sims tagged (member->NewMember, superchat->SuperChat). Good Dog ONE integration test: AlertLayerTests (RealApp STA, real WPF raster) — ParsePage classifies all six kinds + cadence fields; None rows enqueue nothing; six events play pairwise-distinct moving frames then drain to null. Gate: clean build 0 warnings; full suite 316/317, the one failure (LayerReorderPersistenceTests.RealMouseDrag) repros on the clean tree — the known environmental class (real-mouse-drag no-ops with a game/fullscreen window focused). References (OBS/overlay ecosystem): - streamList semantics: https://developers.google.com/youtube/v3/live/docs/liveChatMessages/streamList - OBS alert-box pattern (designated celebration zone, idle transparent): creator-chosen model
389 lines
17 KiB
C#
389 lines
17 KiB
C#
using System.Globalization;
|
||
using System.Windows;
|
||
using System.Windows.Media;
|
||
using System.Windows.Media.Imaging;
|
||
using ytLive.Models;
|
||
|
||
namespace ytLive.Services;
|
||
|
||
/// <summary>
|
||
/// Renders one event's celebration animation into a BGRA8 frame for the AlertBox.
|
||
/// Six event kinds, six UNIQUE animations — each a distinct gesture, palette and
|
||
/// layout (the creator's ruling: these are the monetization shout-outs and free
|
||
/// branded product placement; a one-template recoloring loses all interest in two
|
||
/// iterations). Each card carries the bottom-right brand line so every broadcast
|
||
/// of an event is an in-video ad placement.
|
||
///
|
||
/// <paramref name="t01"/> walks 0→1 over the whole animation; each kind splits it
|
||
/// into enter / hold / exit phases with its own easing.
|
||
/// </summary>
|
||
public sealed class AlertRenderer
|
||
{
|
||
private const double EnterEnd = 0.16;
|
||
private const double ExitStart = 0.80;
|
||
private const string Brand = "made with LlamaCasty!";
|
||
|
||
private static readonly CultureInfo Invariant = CultureInfo.InvariantCulture;
|
||
private static readonly Color White = Colors.White;
|
||
private static readonly Color[] ConfettiPalette =
|
||
{
|
||
Color.FromRgb(0xf4, 0x2f, 0x5f), Color.FromRgb(0xff, 0xd1, 0x66),
|
||
Color.FromRgb(0x38, 0xb6, 0xf8), Color.FromRgb(0xa7, 0x8b, 0xfa),
|
||
Color.FromRgb(0x39, 0xd3, 0x53),
|
||
};
|
||
|
||
public VideoFrame Render(ChatMessage e, double t01, int width, int height)
|
||
{
|
||
return e.Kind switch
|
||
{
|
||
ChatEventKind.SuperChat => SuperChat(e, t01, width, height),
|
||
ChatEventKind.SuperSticker => SuperSticker(e, t01, width, height),
|
||
ChatEventKind.NewMember => NewMember(e, t01, width, height),
|
||
ChatEventKind.MemberGift => MemberGift(e, t01, width, height),
|
||
ChatEventKind.GiftReceived => GiftReceived(e, t01, width, height),
|
||
ChatEventKind.MemberMilestone => Milestone(e, t01, width, height),
|
||
_ => SuperChat(e, t01, width, height),
|
||
};
|
||
}
|
||
|
||
private static VideoFrame SuperChat(ChatMessage e, double t, int w, int h)
|
||
{
|
||
var dpi = 96.0;
|
||
var visual = new DrawingVisual();
|
||
using (var ctx = visual.RenderOpen())
|
||
{
|
||
var margin = Math.Max(w, h) * 0.03;
|
||
var cardX = margin;
|
||
var cardW = w - margin * 2;
|
||
var cardH = h - margin * 2;
|
||
var cardCX = cardX + cardW / 2;
|
||
|
||
var enter = Clamp01(t / EnterEnd);
|
||
var exit = Clamp01((t - ExitStart) / (1 - ExitStart));
|
||
var exitEase = EaseInCubic(exit);
|
||
var cardY = Lerp(h + cardH, margin, EaseOutCubic(enter));
|
||
cardY = Lerp(cardY, -cardH, exitEase);
|
||
|
||
DrawCard(ctx, cardX, cardY, cardW, cardH, "#0d3b2b", "#14532d", 14);
|
||
DrawBrand(ctx, cardX, cardY, cardW, cardH);
|
||
|
||
var shownAmount = e.SuperChatDisplayString
|
||
?? (e.SuperChatCurrency is { Length: > 0 } cur ? $"{cur}{e.SuperChatAmount:0.00}" : $"${e.SuperChatAmount:0.00}");
|
||
var fontSize = cardH * 0.24;
|
||
DrawCentered(ctx, shownAmount, cardCX, cardY + cardH * 0.24, fontSize, FontWeights.Bold, Color.FromRgb(0xff, 0xd1, 0x66), cardW);
|
||
|
||
DrawCentered(ctx, e.AuthorName, cardCX, cardY + cardH * 0.56, cardH * 0.13, FontWeights.SemiBold, White, cardW);
|
||
DrawCentered(ctx, e.Message, cardCX, cardY + cardH * 0.74, cardH * 0.10, FontWeights.Normal, Color.FromRgb(0xdd, 0xff, 0xf0), cardW * 0.92);
|
||
|
||
if (t >= EnterEnd && t <= ExitStart)
|
||
{
|
||
var sweep = (t - EnterEnd) / (ExitStart - EnterEnd);
|
||
var shineX = cardX + (sweep * 2 - 0.5) * cardW;
|
||
ctx.DrawRectangle(
|
||
new LinearGradientBrush(
|
||
Colors.Transparent,
|
||
Color.FromArgb(0x30, 0xff, 0xff, 0xff),
|
||
new Point(0, 0),
|
||
new Point(1, 0)),
|
||
null,
|
||
new Rect(shineX - cardW * 0.25, cardY, cardW * 0.5, cardH));
|
||
}
|
||
}
|
||
return CreateFrame(visual, w, h, dpi);
|
||
}
|
||
|
||
private static VideoFrame SuperSticker(ChatMessage e, double t, int w, int h)
|
||
{
|
||
var dpi = 96.0;
|
||
var visual = new DrawingVisual();
|
||
using (var ctx = visual.RenderOpen())
|
||
{
|
||
var margin = Math.Max(w, h) * 0.04;
|
||
var cardW = w - margin * 2;
|
||
var cardH = h - margin * 2;
|
||
var cardCX = w / 2.0;
|
||
|
||
var enter = Clamp01(t / EnterEnd);
|
||
var exit = Clamp01((t - ExitStart) / (1 - ExitStart));
|
||
var scale = EaseOutBack(enter) * (0.85 + 0.15 * (1 - exit));
|
||
if (t > EnterEnd && t <= ExitStart)
|
||
scale = 1.0 + 0.02 * Math.Sin(t * 26.0);
|
||
var alpha = 1.0 - exit;
|
||
|
||
ctx.PushOpacity(alpha);
|
||
|
||
var slick = new RotateTransform(-6, w / 2.0, h / 2.0);
|
||
ctx.PushTransform(slick);
|
||
ctx.PushTransform(new ScaleTransform(scale, scale, w / 2.0, h / 2.0));
|
||
|
||
DrawCard(ctx, margin, margin, cardW, cardH, "#4a044e", "#7e22ce", 20);
|
||
DrawBrand(ctx, margin, margin, cardW, cardH);
|
||
|
||
var desc = string.IsNullOrWhiteSpace(e.SuperStickerDescription) ? "SUPER STICKER" : e.SuperStickerDescription.ToUpperInvariant();
|
||
DrawCentered(ctx, desc, cardCX, h / 2.0 - cardH * 0.12, cardH * 0.17, FontWeights.Bold, White, cardW * 0.9);
|
||
|
||
DrawCentered(ctx, e.AuthorName, cardCX, h / 2.0 + cardH * 0.16, cardH * 0.12, FontWeights.SemiBold, Color.FromRgb(0xff, 0xb3, 0xd9), cardW * 0.9);
|
||
var tier = e.SuperChatDisplayString ?? (e.SuperChatCurrency is { Length: > 0 } cur ? $"{cur}{e.SuperChatAmount:0.00}" : null);
|
||
if (!string.IsNullOrWhiteSpace(tier))
|
||
DrawCentered(ctx, tier, cardCX, h / 2.0 + cardH * 0.32, cardH * 0.10, FontWeights.Bold, Color.FromRgb(0xff, 0xd1, 0x66), cardW * 0.9);
|
||
|
||
ctx.Pop();
|
||
ctx.Pop();
|
||
ctx.Pop();
|
||
}
|
||
return CreateFrame(visual, w, h, dpi);
|
||
}
|
||
|
||
private static VideoFrame NewMember(ChatMessage e, double t, int w, int h)
|
||
{
|
||
var dpi = 96.0;
|
||
var visual = new DrawingVisual();
|
||
using (var ctx = visual.RenderOpen())
|
||
{
|
||
var margin = Math.Max(w, h) * 0.035;
|
||
var cardW = w - margin * 2;
|
||
var cardH = h - margin * 2;
|
||
var cardCX = w / 2.0;
|
||
|
||
var enter = Clamp01(t / EnterEnd);
|
||
var exit = Clamp01((t - ExitStart) / (1 - ExitStart));
|
||
var cardY = Lerp(-cardH, margin, EaseOutCubic(enter));
|
||
cardY = Lerp(cardY, h, EaseInCubic(exit));
|
||
|
||
DrawCard(ctx, margin, cardY, cardW, cardH, "#2e1065", "#5b21b6", 16);
|
||
DrawBrand(ctx, margin, cardY, cardW, cardH);
|
||
|
||
if (t <= ExitStart)
|
||
{
|
||
var bandP = EaseOutCubic(Clamp01(t / 0.22));
|
||
ctx.DrawRectangle(
|
||
new LinearGradientBrush(Color.FromArgb(0, 0xff, 0xff, 0xff), Colors.Transparent, new Point(0, 0), new Point(0, 1)),
|
||
null,
|
||
new Rect(margin, cardY + cardH * 0.30, cardW * bandP, cardH * 0.18));
|
||
}
|
||
|
||
DrawCentered(ctx, e.AuthorName, cardCX, cardY + cardH * 0.36, cardH * 0.22, FontWeights.Bold, White, cardW * 0.9);
|
||
DrawCentered(ctx, "JOINED THE MEMBERSHIP", cardCX, cardY + cardH * 0.62, cardH * 0.11, FontWeights.SemiBold, Color.FromRgb(0xf5, 0xd0, 0xfe), cardW * 0.9);
|
||
|
||
var level = string.IsNullOrWhiteSpace(e.MembershipLevel) ? "MEMBER" : e.MembershipLevel.ToUpperInvariant();
|
||
DrawCentered(ctx, level, cardCX, cardY + cardH * 0.80, cardH * 0.10, FontWeights.Bold, Color.FromRgb(0xff, 0xd1, 0x66), cardW * 0.9);
|
||
}
|
||
return CreateFrame(visual, w, h, dpi);
|
||
}
|
||
|
||
private static VideoFrame MemberGift(ChatMessage e, double t, int w, int h)
|
||
{
|
||
var dpi = 96.0;
|
||
var visual = new DrawingVisual();
|
||
using (var ctx = visual.RenderOpen())
|
||
{
|
||
var margin = Math.Max(w, h) * 0.035;
|
||
var cardW = w - margin * 2;
|
||
var cardH = h - margin * 2;
|
||
var cardCX = w / 2.0;
|
||
|
||
var enter = Clamp01(t / EnterEnd);
|
||
var exit = Clamp01((t - ExitStart) / (1 - ExitStart));
|
||
var cardX = Lerp(-cardW, margin, EaseOutCubic(enter));
|
||
cardX = Lerp(cardX, -cardW, EaseInCubic(exit));
|
||
|
||
DrawCard(ctx, cardX, margin, cardW, cardH, "#422006", "#a16207", 16);
|
||
DrawBrand(ctx, cardX, margin, cardW, cardH);
|
||
|
||
var count = Math.Max(1, e.GiftNewMembersCount);
|
||
var spread = 1.0 - exit;
|
||
var hero = count > 1 ? $"×{count}" : "MEMBERSHIP";
|
||
DrawCentered(ctx, hero, cardCX, margin + cardH * 0.24, cardH * 0.26, FontWeights.Bold, Color.FromRgb(0xff, 0xd1, 0x66), cardW * 0.8);
|
||
DrawCentered(ctx, $"{e.AuthorName} is gifting", cardCX, margin + cardH * 0.52, cardH * 0.11, FontWeights.SemiBold, White, cardW * 0.9);
|
||
|
||
var chipCount = Math.Min(count, 6);
|
||
for (var i = 0; i < chipCount; i++)
|
||
{
|
||
var slot = (chipCount - 1) * 0.5;
|
||
var gap = cardH * 0.13 * EaseOutCubic(Clamp01(t / 0.30)) * spread;
|
||
var chipX = cardCX + (i - slot) * gap;
|
||
var chipY = margin + cardH * 0.70 + Math.Sin((i + t * 6) * 2.4) * cardH * 0.015;
|
||
ctx.DrawRoundedRectangle(
|
||
new SolidColorBrush(Color.FromRgb(0xff, 0xf3, 0xc4)),
|
||
null,
|
||
new Rect(chipX - cardH * 0.05, chipY, cardH * 0.10, cardH * 0.10),
|
||
cardH * 0.03, cardH * 0.03);
|
||
}
|
||
}
|
||
return CreateFrame(visual, w, h, dpi);
|
||
}
|
||
|
||
private static VideoFrame GiftReceived(ChatMessage e, double t, int w, int h)
|
||
{
|
||
var dpi = 96.0;
|
||
var visual = new DrawingVisual();
|
||
using (var ctx = visual.RenderOpen())
|
||
{
|
||
var margin = Math.Max(w, h) * 0.04;
|
||
var cardW = w - margin * 2;
|
||
var cardH = h - margin * 2;
|
||
var cardCX = w / 2.0;
|
||
|
||
var enter = Clamp01(t / (EnterEnd * 1.4));
|
||
var exit = Clamp01((t - ExitStart) / (1 - ExitStart));
|
||
var scale = 0.5 + 0.5 * EaseOutBack(enter);
|
||
var alpha = Math.Min(1.0, enter * 1.6) * (1 - exit);
|
||
|
||
ctx.PushOpacity(alpha);
|
||
ctx.PushTransform(new ScaleTransform(scale, scale, w / 2.0, h / 2.0));
|
||
|
||
DrawCard(ctx, margin, margin, cardW, cardH, "#14532d", "#4c1d95", 18);
|
||
DrawBrand(ctx, margin, margin, cardW, cardH);
|
||
|
||
// Confetti falling inside the card during the hold (deterministic seed).
|
||
if (t >= EnterEnd && t <= ExitStart)
|
||
{
|
||
var hold = (t - EnterEnd) / (ExitStart - EnterEnd);
|
||
for (var i = 0; i < 14; i++)
|
||
{
|
||
var seed = i * 37 + 11;
|
||
var dotX = margin + cardW * (((seed * 53) % 97) / 97.0);
|
||
var dotY = margin + cardH * (((seed + hold * 0.85) % 1.0));
|
||
var size = cardH * 0.02 + (i % 3) * cardH * 0.006;
|
||
ctx.DrawRectangle(
|
||
new SolidColorBrush(ConfettiPalette[i % ConfettiPalette.Length]),
|
||
null,
|
||
new Rect(dotX, dotY, size, size));
|
||
}
|
||
}
|
||
|
||
DrawCentered(ctx, "GIFTED MEMBERSHIP", cardCX, margin + cardH * 0.22, cardH * 0.12, FontWeights.Bold, Color.FromRgb(0xff, 0xd1, 0x66), cardW * 0.9);
|
||
var giver = string.IsNullOrWhiteSpace(e.GifterName) ? e.AuthorName : e.GifterName;
|
||
DrawCentered(ctx, giver, cardCX, margin + cardH * 0.42, cardH * 0.20, FontWeights.Bold, White, cardW * 0.9);
|
||
DrawCentered(ctx, "gave you a membership — celebrate!", cardCX, margin + cardH * 0.68, cardH * 0.11, FontWeights.Normal, Color.FromRgb(0xdb, 0xff, 0xf0), cardW * 0.9);
|
||
|
||
ctx.Pop();
|
||
ctx.Pop();
|
||
}
|
||
return CreateFrame(visual, w, h, dpi);
|
||
}
|
||
|
||
private static VideoFrame Milestone(ChatMessage e, double t, int w, int h)
|
||
{
|
||
var dpi = 96.0;
|
||
var visual = new DrawingVisual();
|
||
using (var ctx = visual.RenderOpen())
|
||
{
|
||
var margin = Math.Max(w, h) * 0.04;
|
||
var cardW = w - margin * 2;
|
||
var cardH = h - margin * 2;
|
||
var cardCX = w / 2.0;
|
||
|
||
var enter = Clamp01(t / EnterEnd);
|
||
var exit = Clamp01((t - ExitStart) / (1 - ExitStart));
|
||
var cardY = Lerp(h + cardH, margin, EaseOutCubic(enter));
|
||
cardY = Lerp(cardY, -cardH, EaseInCubic(exit));
|
||
|
||
DrawCard(ctx, margin, cardY, cardW, cardH, "#431407", "#c2410c", 14);
|
||
DrawBrand(ctx, margin, cardY, cardW, cardH);
|
||
|
||
var hero = e.MilestoneMonths > 0 ? $"{e.MilestoneMonths}" : "MILESTONE";
|
||
DrawCentered(ctx, hero, cardCX, cardY + cardH * 0.26, cardH * 0.30, FontWeights.Bold, Color.FromRgb(0xff, 0xdd, 0x66), cardW * 0.8);
|
||
|
||
var sub = e.MilestoneMonths > 0 ? "MONTHS STRONG" : "SUPPORTING THE STREAM";
|
||
DrawCentered(ctx, sub, cardCX, cardY + cardH * 0.52, cardH * 0.11, FontWeights.SemiBold, White, cardW * 0.9);
|
||
DrawCentered(ctx, "— " + e.AuthorName + " —", cardCX, cardY + cardH * 0.70, cardH * 0.10, FontWeights.Normal, Color.FromRgb(0xff, 0xe8, 0xd0), cardW * 0.9);
|
||
|
||
// Tenure bar: grows through the first half, retracts on exit.
|
||
var barP = Clamp01((t - 0.10) / 0.55) * (1 - exit);
|
||
var barW = cardW * 0.6 * barP;
|
||
ctx.DrawRoundedRectangle(
|
||
new SolidColorBrush(Color.FromRgb(0xff, 0xd1, 0x66)),
|
||
null,
|
||
new Rect(cardCX - barW / 2, cardY + cardH * 0.84, barW, cardH * 0.045),
|
||
4, 4);
|
||
}
|
||
return CreateFrame(visual, w, h, dpi);
|
||
}
|
||
|
||
// ─── Shared drawing ───
|
||
|
||
private static void DrawCard(DrawingContext ctx, double x, double y, double w, double h, string fromHex, string toHex, double radius)
|
||
{
|
||
var brush = new LinearGradientBrush(ParseColor(fromHex), ParseColor(toHex), new Point(0, 0), new Point(1, 1));
|
||
ctx.DrawRoundedRectangle(brush, new Pen(new SolidColorBrush(Color.FromArgb(0x66, 0xff, 0xff, 0xff)), Math.Max(1, radius * 0.06)), new Rect(x, y, w, h), radius, radius);
|
||
}
|
||
|
||
private static void DrawBrand(DrawingContext ctx, double x, double y, double w, double h)
|
||
{
|
||
var formatted = new FormattedText(
|
||
Brand, Invariant, FlowDirection.LeftToRight,
|
||
new Typeface(new FontFamily("Segoe UI"), FontStyles.Normal, FontWeights.Normal, FontStretches.Normal),
|
||
Math.Max(9, h * 0.055),
|
||
new SolidColorBrush(Color.FromArgb(0x88, 0xff, 0xff, 0xff)),
|
||
96.0);
|
||
ctx.DrawText(formatted, new Point(x + w - formatted.Width - h * 0.08, y + h - formatted.Height - h * 0.05));
|
||
}
|
||
|
||
private static void DrawCentered(DrawingContext ctx, string text, double cx, double cy, double size, FontWeight weight, Color color, double maxWidth)
|
||
{
|
||
var formatted = new FormattedText(
|
||
text, Invariant, FlowDirection.LeftToRight,
|
||
new Typeface(new FontFamily("Segoe UI"), FontStyles.Normal, weight, FontStretches.Normal),
|
||
size,
|
||
new SolidColorBrush(color),
|
||
96.0);
|
||
formatted.MaxTextWidth = maxWidth;
|
||
formatted.MaxLineCount = 1;
|
||
formatted.Trimming = TextTrimming.CharacterEllipsis;
|
||
ctx.DrawText(formatted, new Point(cx - formatted.Width / 2, cy - formatted.Height / 2));
|
||
}
|
||
|
||
private static Color ParseColor(string hex)
|
||
{
|
||
hex = hex.Trim().TrimStart('#');
|
||
if (hex.Length == 6)
|
||
{
|
||
var r = byte.Parse(hex.AsSpan(0, 2), NumberStyles.HexNumber);
|
||
var g = byte.Parse(hex.AsSpan(2, 2), NumberStyles.HexNumber);
|
||
var b = byte.Parse(hex.AsSpan(4, 2), NumberStyles.HexNumber);
|
||
return Color.FromRgb(r, g, b);
|
||
}
|
||
if (hex.Length == 8)
|
||
{
|
||
var a = byte.Parse(hex.AsSpan(0, 2), NumberStyles.HexNumber);
|
||
var r = byte.Parse(hex.AsSpan(2, 2), NumberStyles.HexNumber);
|
||
var g = byte.Parse(hex.AsSpan(4, 2), NumberStyles.HexNumber);
|
||
var b = byte.Parse(hex.AsSpan(6, 2), NumberStyles.HexNumber);
|
||
return Color.FromArgb(a, r, g, b);
|
||
}
|
||
return Colors.White;
|
||
}
|
||
|
||
private static VideoFrame CreateFrame(DrawingVisual visual, int width, int height, double dpi)
|
||
{
|
||
var rtb = new RenderTargetBitmap(width, height, dpi, dpi, PixelFormats.Pbgra32);
|
||
rtb.Render(visual);
|
||
|
||
var stride = width * 4;
|
||
var pixels = new byte[stride * height];
|
||
rtb.CopyPixels(pixels, stride, 0);
|
||
|
||
for (var i = 0; i < pixels.Length; i += 4)
|
||
{
|
||
(pixels[i], pixels[i + 2]) = (pixels[i + 2], pixels[i]);
|
||
}
|
||
|
||
return new VideoFrame(width, height, pixels);
|
||
}
|
||
|
||
private static double Clamp01(double v) => Math.Clamp(v, 0.0, 1.0);
|
||
|
||
private static double Lerp(double a, double b, double p) => a + (b - a) * p;
|
||
|
||
private static double EaseInCubic(double p) => p * p * p;
|
||
|
||
private static double EaseOutCubic(double p) => 1 - Math.Pow(1 - p, 3);
|
||
|
||
private static double EaseOutBack(double p)
|
||
{
|
||
const double c1 = 1.70158;
|
||
const double c3 = c1 + 1;
|
||
return 1 + c3 * Math.Pow(p - 1, 3) + c1 * Math.Pow(p - 1, 2);
|
||
}
|
||
} |