const SAFE_MARGIN: number = 100;
if (newX < SAFE_MARGIN || newX > screenWidth - SAFE_MARGIN) {
currentAngle = Math.PI - currentAngle;
}
if (newY < SAFE_MARGIN || newY > screenHeight - SAFE_MARGIN) {
currentAngle = -currentAngle;
}
const finalX: number = lastX + distance * Math.cos(currentAngle);
const finalY: number = lastY + distance * Math.sin(currentAngle);
const clampedX: number = Math.max(SAFE_MARGIN, Math.min(screenWidth - SAFE_MARGIN, finalX));
const clampedY: number = Math.max(SAFE_MARGIN, Math.min(screenHeight - SAFE_MARGIN, finalY));
function isTooClose(candidate: CircleSegment, circles: CircleSegment[]): boolean {
const recent: CircleSegment[] = circles.slice(-5);
for (const c of recent) {
const dx: number = candidate.x - c.x;
const dy: number = candidate.y - c.y;
const dist: number = Math.sqrt(dx * dx + dy * dy);
if (dist < 70) {
return true;
}
}
return false;
}
for (let attempt: number = 0; attempt < 5; attempt++) {
const cand: CircleSegment = generateCandidate();
if (!isTooClose(cand, circles)) {
circles.push(cand);
return;
}
}
const fallbackX: number = last.x + (MIN_SPACING + 10) * Math.cos(currentAngle);
const fallbackY: number = last.y + (MIN_SPACING + 10) * Math.sin(currentAngle);
circles.push(new CircleSegment(fallbackX, fallbackY, 25));
class CircleSegment {
x: number;
y: number;
radius: number;
constructor(x: number, y: number, radius: number = 25.0) {
this.x = x;
this.y = y;
this.radius = radius;
}
}
@Entry
@Component
struct TrackDemoApp {
build() {
Column() {
TrackGenerator()
}
.width('100%')
.height('100%')
.backgroundColor('#000000');
}
}
@Component
struct TrackGenerator {
@State private circles: CircleSegment[] = [];
private currentAngle: number = Math.PI / 4;
private initialized: boolean = false;
private canvasWidth: number = 0;
private canvasHeight: number = 0;
private readonly MIN_SPACING: number = 90.0;
private readonly MAX_SPACING: number = 130.0;
private readonly MAX_ANGLE_DELTA: number = Math.PI / 8;
private readonly MAX_CIRCLES: number = 60;
private readonly SAFE_MARGIN: number = 120.0;
private nextDouble(): number {
return Math.random();
}
private initializeTrack(): void {
if (this.initialized) return;
this.initialized = true;
this.canvasWidth = 1080;
this.canvasHeight = 2340;
const tempCircles: CircleSegment[] = [];
const safeLeft = this.SAFE_MARGIN;
const safeRight = this.canvasWidth - this.SAFE_MARGIN;
const safeTop = this.SAFE_MARGIN;
const safeBottom = this.canvasHeight - this.SAFE_MARGIN;
let x = this.canvasWidth * 0.4;
let y = this.canvasHeight * 0.4;
for (let i = 0; i < 8; i++) {
const radius = 18.0 + this.nextDouble() * 20.0;
if (i === 0) {
tempCircles.push(new CircleSegment(x, y, radius));
} else {
const angleDelta = (2 * this.nextDouble() - 1) * this.MAX_ANGLE_DELTA;
this.currentAngle += angleDelta;
const distance = this.MIN_SPACING + this.nextDouble() * (this.MAX_SPACING - this.MIN_SPACING);
let newX = x + distance * Math.cos(this.currentAngle);
let newY = y + distance * Math.sin(this.currentAngle);
if (newX < safeLeft || newX > safeRight) {
this.currentAngle = Math.PI - this.currentAngle;
newX = x + distance * Math.cos(this.currentAngle);
}
if (newY < safeTop || newY > safeBottom) {
this.currentAngle = -this.currentAngle;
newY = y + distance * Math.sin(this.currentAngle);
}
newX = Math.max(safeLeft, Math.min(safeRight, newX));
newY = Math.max(safeTop, Math.min(safeBottom, newY));
tempCircles.push(new CircleSegment(newX, newY, radius));
x = newX;
y = newY;
}
}
this.circles = tempCircles;
}
private extendTrack(): void {
if (this.circles.length === 0 || this.canvasWidth === 0) return;
const tempCircles = [...this.circles];
const last = tempCircles[tempCircles.length - 1];
const safeLeft = this.SAFE_MARGIN;
const safeRight = this.canvasWidth - this.SAFE_MARGIN;
const safeTop = this.SAFE_MARGIN;
const safeBottom = this.canvasHeight - this.SAFE_MARGIN;
let newNodeAdded = false;
for (let attempt = 0; attempt < 5; attempt++) {
const angleDelta = (2 * this.nextDouble() - 1) * this.MAX_ANGLE_DELTA;
this.currentAngle += angleDelta;
const distance = this.MIN_SPACING + this.nextDouble() * (this.MAX_SPACING - this.MIN_SPACING);
let newX = last.x + distance * Math.cos(this.currentAngle);
let newY = last.y + distance * Math.sin(this.currentAngle);
if (newX < safeLeft || newX > safeRight) {
this.currentAngle = Math.PI - this.currentAngle;
newX = last.x + distance * Math.cos(this.currentAngle);
}
if (newY < safeTop || newY > safeBottom) {
this.currentAngle = -this.currentAngle;
newY = last.y + distance * Math.sin(this.currentAngle);
}
newX = Math.max(safeLeft, Math.min(safeRight, newX));
newY = Math.max(safeTop, Math.min(safeBottom, newY));
const newRadius = 18.0 + this.nextDouble() * 20.0;
const candidate = new CircleSegment(newX, newY, newRadius);
let tooClose = false;
const recentCircles = tempCircles.slice(Math.max(0, tempCircles.length - 5));
for (const c of recentCircles) {
const dx = candidate.x - c.x;
const dy = candidate.y - c.y;
if (Math.sqrt(dx * dx + dy * dy) < 70.0) {
tooClose = true;
break;
}
}
if (!tooClose) {
tempCircles.push(candidate);
if (tempCircles.length > this.MAX_CIRCLES) {
tempCircles.shift();
}
newNodeAdded = true;
break;
}
}
if (newNodeAdded) {
this.circles = tempCircles;
}
}
private drawTrack(ctx: CanvasRenderingContext2D): void {
if (this.circles.length === 0) return;
const total = this.circles.length;
for (let i = 0; i < this.circles.length - 1; i++) {
const a = this.circles[i];
const b = this.circles[i + 1];
const progress = i / total;
const alpha = Math.max(30, Math.min(150, Math.floor(150 * (1 - progress))));
ctx.beginPath();
ctx.moveTo(a.x, a.y);
ctx.lineTo(b.x, b.y);
ctx.strokeStyle = `rgba(200, 200, 255, ${alpha / 255})`;
ctx.lineWidth = 2.5;
ctx.stroke();
}
for (let i = 0; i < this.circles.length; i++) {
const c = this.circles[i];
const progress = i / total;
const r = Math.max(100, Math.min(255, Math.floor(100 + 100 * progress)));
const g = Math.max(50, Math.min(150, Math.floor(150 - 100 * progress)));
const b = 255;
const alpha = Math.max(100, Math.min(200, Math.floor(200 * (1 - progress))));
ctx.beginPath();
ctx.arc(c.x, c.y, c.radius, 0, 2 * Math.PI);
ctx.fillStyle = `rgba(${r}, ${g}, ${b}, ${alpha / 255})`;
ctx.fill();
ctx.beginPath();
ctx.arc(c.x, c.y, c.radius, 0, 2 * Math.PI);
ctx.strokeStyle = `rgb(${r}, ${g}, ${b})`;
ctx.lineWidth = 3;
ctx.stroke();
}
}
aboutToAppear() {
this.initializeTrack();
}
build() {
Stack() {
Canvas(this.drawTrack.bind(this))
.width('100%')
.height('100%')
.gesture(TapGesture().onAction(() => {
this.extendTrack();
}))
Text('点击屏幕延伸轨道')
.fontSize(20)
.fontColor('#B3FFFFFF')
.position({ x: 0, y: 60 })
.width('100%')
.textAlign(TextAlign.Center)
}
.width('100%')
.height('100%');
}
}