Script: Double Arc - Poyo-SSB/osu-playground GitHub Wiki

function start() {
	var rotation = Playground.AddFloat('rotation', 53);

    var majorCenter = Playground.AddVector2('majorCenter', new Vector2(180, 200));
	var majorRadius = Playground.AddFloat('majorRadius', 100);
	var majorArc = Playground.AddFloat('majorArc', 290);

    var minorRadius = Playground.AddFloat('minorRadius', 75);
	var minorArc = Playground.AddFloat('minorArc', 275);

	Playground.AddOptionFloat(rotation, 'Rotation', 1, 0, 360);

	Playground.AddOptionVector2(majorCenter, 'Major center');
	Playground.AddOptionFloat(majorRadius, 'Major radius', 0, 40, 150);
	Playground.AddOptionFloat(majorArc, 'Major arc', 1, 0, 360);

	Playground.AddOptionFloat(minorRadius, 'Minor radius', 0, 40, 150);
	Playground.AddOptionFloat(minorArc, 'Minor arc', 1, 0, 360);
}

function update() {
	var rotation = Playground.GetValueFloat('rotation');
	var majorCenter = Playground.GetValueVector2('majorCenter');
	var majorRadius = Playground.GetValueFloat('majorRadius');
	var majorArc = Playground.GetValueFloat('majorArc');
	var minorRadius = Playground.GetValueFloat('minorRadius');
	var minorArc = Playground.GetValueFloat('minorArc');

	var majorPoints = createCircle(majorRadius, majorArc, 1);
	var minorPoints = createCircle(minorRadius, minorArc, -1);

	var majorArcDelta = (majorArc % 90) / 90;

	if (majorArc < 360) {
		var splitMajorArc = splitBezier(
			majorPoints[majorPoints.length - 4],
			majorPoints[majorPoints.length - 3],
			majorPoints[majorPoints.length - 2],
			majorPoints[majorPoints.length - 1],
			majorArcDelta);

		majorPoints[majorPoints.length - 4] = splitMajorArc[0];
		majorPoints[majorPoints.length - 3] = splitMajorArc[1];
		majorPoints[majorPoints.length - 2] = splitMajorArc[2];
		majorPoints[majorPoints.length - 1] = splitMajorArc[3];
	}

	var minorArcDelta = (minorArc % 90) / 90;

	if (minorArc < 360) {
		var splitMinorArc = splitBezier(
			minorPoints[minorPoints.length - 4],
			minorPoints[minorPoints.length - 3],
			minorPoints[minorPoints.length - 2],
			minorPoints[minorPoints.length - 1],
			minorArcDelta);

		minorPoints[minorPoints.length - 4] = splitMinorArc[0];
		minorPoints[minorPoints.length - 3] = splitMinorArc[1];
		minorPoints[minorPoints.length - 2] = splitMinorArc[2];
		minorPoints[minorPoints.length - 1] = splitMinorArc[3];
	}

	majorPoints = majorPoints.map(function (p) {
		return rotate(
			add(p, majorCenter),
			majorCenter,
			rotation * Math.PI / 180);
	});

	var majorEnd = majorPoints[majorPoints.length - 1];
	var minorCenter = add(majorEnd, scale(normalize(subtract(majorEnd, majorCenter)), minorRadius));

	minorPoints = minorPoints.map(function (p) {
		return rotate(
			add(p, minorCenter),
			minorCenter,
			(majorArc + rotation) * Math.PI / 180 + Math.PI);
	});

	// Floating-point rounding error causes the major arc and minor arc to diverge, leading to an improper join.
	// This is rectified manually.
	var difference = subtract(majorPoints[majorPoints.length - 1], minorPoints[0]);

	minorPoints = minorPoints.map(function (p) {
		return add(p, difference);
	});

	Playground.AddSlider(CurveType.Bezier, majorPoints.concat(minorPoints));
}

function createCircle(radius, arc, y) {
	var points = []

	// Optimal cubic bézier anchor points for an approximation of a circular quadrant.
	var c = radius * 4 / 3 * (Math.sqrt(2) - 1);

	if (arc >= 90 * 0) {
		points.push(new Vector2(radius, 0));
		points.push(new Vector2(radius, y * c));
		points.push(new Vector2(c, y * radius));
		points.push(new Vector2(0, y * radius));
	}
	if (arc >= 90 * 1) {
		points.push(new Vector2(0, y * radius));
		points.push(new Vector2(-c, y * radius));
		points.push(new Vector2(-radius, y * c));
		points.push(new Vector2(-radius, 0));
	}
	if (arc >= 90 * 2) {
		points.push(new Vector2(-radius, 0));
		points.push(new Vector2(-radius, y * -c));
		points.push(new Vector2(-c, y * -radius));
		points.push(new Vector2(0, y * -radius));
	}
	if (arc >= 90 * 3) {
		points.push(new Vector2(0, y * -radius));
		points.push(new Vector2(c, y * -radius));
		points.push(new Vector2(radius, y * -c));
		points.push(new Vector2(radius, 0));
	}

	return points;
}

function splitBezier(p1, p2, p3, p4, t) {
	var p12 = add(scale(subtract(p2, p1), t), p1);
	var p23 = add(scale(subtract(p3, p2), t), p2);
	var p34 = add(scale(subtract(p4, p3), t), p3);
	var p123 = add(scale(subtract(p23, p12), t), p12);
	var p234 = add(scale(subtract(p34, p23), t), p23);
	var p1234 = add(scale(subtract(p234, p123), t), p123);

	return [
		p1,
		p12,
		p123,
		p1234
	];
}