如何围绕椭圆旋转文本?
How do I rotate text around an oval?
我想围绕椭圆旋转文本。每个字符都会出现一些轻微的变形,但椭圆形变得越极端(即越不完美的圆圈)。理想情况下,此方法可以在任何高宽比的椭圆形甚至其他形状(例如圆角矩形)上很好地呈现文本。
到目前为止我的方法是:
- 找到椭圆的边
- 使用三角函数围绕扭曲的椭圆旋转一个点
- 在这个位置画
text()
- 根据它的位置旋转这个
text()
- 通过
for
循环执行此操作,遍历提供的字符串中的每个字符。
字符获得奇怪的间距(即使使用 textWidth
)和奇怪的旋转(即使动态计算)。您可以在下面的代码片段中看到这一点,特别是 'spinning' 的 'nn' 和 'piece' 的 'ie'。
let canvasWidth = 400;
let canvasHeight = 400;
let spinSpeed = 0.25;
let ellipseWidth = 280;
let ellipseHeight = 200;
let angle = 0;
let sourceText = "A beautiful piece of spinning text. ";
let sourceCharacters = sourceText.toUpperCase().split("");
function setup() {
createCanvas(canvasWidth, canvasHeight);
angleMode(DEGREES);
textSize(18);
textAlign(LEFT, BASELINE);
}
function draw() {
background(0);
// Draw an ellipse
stroke("rgba(255, 255, 255, 0.15)");
noFill();
ellipse(canvasWidth / 2, canvasHeight / 2, ellipseWidth, ellipseHeight);
// Prepare for operations around circle
translate(canvasWidth / 2, canvasHeight / 2);
// Create a revolving angle
if (angle < 360) {
angle += spinSpeed;
} else {
angle = 0;
}
// Set variables for trigonometry
let widthR = ellipseWidth / 2;
let heightR = ellipseHeight / 2;
let dx = widthR * cos(angle);
let dy = heightR * sin(angle);
// Set variable for offsetting each character
let currentOffset = 0;
// Loop through each chracter and place on oval edge
for (let i = 0; i < sourceCharacters.length; i++) {
push();
dx = widthR * cos(angle + currentOffset);
dy = heightR * sin(angle + currentOffset);
translate(dx, dy);
rotate(angle + currentOffset + 90);
stroke("rgba(255, 255, 255, 0.25)");
rect(0, 0, textWidth(sourceCharacters[i]), 10);
fill("white");
text(sourceCharacters[i], 0, 0);
currentOffset += textWidth(sourceCharacters[i]);
pop();
}
}
html,
body {
margin: 0;
padding: 0;
}
canvas {
display: block;
}
<script src="https://cdn.jsdelivr.net/npm/p5@1.4.0/lib/p5.js"></script>
我怀疑字符之间的这种奇怪间距不是由于代码错误造成的。这与您无法制作非常好的地球地图的原因非常简单。在 p5js 中,椭圆和圆并不完美。
在椭圆的某些点,构成像素的圆内的线在某些点比其他点长。我不确定如何解释得好,但我能解释的最好方法是使用与电子游戏中 strafing
相同的概念。
Credits to Dan Violet Sagmiller (Game Developer)
在视频游戏中,游戏开发人员有时会在玩家同时向前和向侧面(对角线)移动时降低玩家的速度。这是因为一些复杂的数学表明扫射会使玩家移动得更快。
扫射发生的唯一原因是游戏开发者改变玩家移动的方式。他们没有使用带向量的三角函数或旋转数学,而是使用转换逻辑 (+, -)。这通常会提高游戏性能。根据您的代码,您还使用了转换逻辑:
translate(dx, dy);
rotate(angle + currentOffset + 90);
基本上,当每个字母在程序的每一帧中移动到其新位置时,由于扫射的工作方式,一些字母比其他字母移动得更远。
我没有任何解决方案,只有降低效果的方法,您可以通过以下方式看到:
减小和增大尺寸;您可以增加椭圆的大小或减小文本的大小和长度。
保持椭圆的比例彼此相似;通过保持宽度和高度彼此接近,字母之间存在间距的效果将会降低。
我希望这能解决您的问题,或者至少有助于减少它的影响。祝你有美好的一天!
这行代码没有意义,可能只是巧合:
currentOffset += textWidth(sourceCharacters[i]);
这是使用以像素为单位的字符宽度作为以度为单位的角度(基于调用 rotate
/sin
/cos
.
正确的实现方式包括在绘制每个字符的点处找到椭圆的切线,使用该切线的斜率来确定每个字符的角度,然后确定旋转的度数space 下一个字母必须使字形边界框的角接触。找到那个字母间距角度似乎很难精确地做到,但我有一个应该工作得相当好的 hack。
let canvasWidth = 400;
let canvasHeight = 400;
let spinSpeed = 0.25;
let ellipseWidth = 280;
let ellipseHeight = 200;
let angle = 0;
let sourceText = "A beautiful piece of spinning text. ";
let sourceCharacters = sourceText.toUpperCase().split("");
function setup() {
createCanvas(canvasWidth, canvasHeight);
angleMode(DEGREES);
textSize(18);
// drawing each letter from it's center point makes determining the angle and
// the spacing a little easier I think.
textAlign(CENTER, BASELINE);
}
function draw() {
background(0);
// Draw an ellipse
stroke("rgba(255, 255, 255, 0.15)");
noFill();
ellipse(canvasWidth / 2, canvasHeight / 2, ellipseWidth, ellipseHeight);
// Prepare for operations around circle
translate(canvasWidth / 2, canvasHeight / 2);
// Create a revolving angle
if (angle < 360) {
angle += spinSpeed;
} else {
angle = 0;
}
// Set variables for trigonometry
const widthR = ellipseWidth / 2;
const heightR = ellipseHeight / 2;
let dx, dy;
// Set variable for offsetting each character
let currentOffset = 0;
// Loop through each chracter and place on oval edge
for (let i = 0; i < sourceCharacters.length; i++) {
push();
/* This isn't the best way to convert an angle to a position on an ellipse
* It causes distortions depending on the tightness of the curve.
dx = widthR * cos(angle + currentOffset);
dy = heightR * sin(angle + currentOffset); */
// This give a more accurate position. See: https://math.stackexchange.com/a/2258243/771335
let r = widthR * heightR / sqrt(widthR ** 2 * sin(angle + currentOffset) ** 2 + heightR ** 2 * cos(angle + currentOffset) ** 2);
dx = r * cos(angle + currentOffset);
dy = r * sin(angle + currentOffset);
translate(dx, dy);
// This is the derivative of the equation for an ellipse.
// Calculating the derivative at X gives us the current slope.
let tangent = -1 * (heightR ** 2) * dx / (widthR ** 2 * sqrt(heightR ** 2 * (widthR ** 2 - dx ** 2) / widthR ** 2));
if (dy < 0) {
tangent *= -1;
}
// Use the tangent slope to determine rotation angle
let rotation = atan2(tangent, 1);
if (dy > 0) {
rotation += 180;
}
rotate(rotation);
let charWidth = textWidth(sourceCharacters[i]);
stroke("rgba(255, 255, 255, 0.25)");
rect(-charWidth / 2, 0, charWidth, -18);
fill("white");
text(sourceCharacters[i], 0, 0);
// find the angle between the vector to the center of the current letter
// and the vector to the center of the next letter projected onto the
// current tangent line. This is a bit of a hack.
if (i + 1 < sourceCharacters.length) {
let spacing = (charWidth + textWidth(sourceCharacters[i + 1])) / 2;
let vCur = createVector(dx, dy);
let vNext =
vCur.copy().add(
createVector(spacing * cos(rotation), spacing * sin(rotation))
);
currentOffset += vCur.angleBetween(vNext);
}
pop();
}
}
html,
body {
margin: 0;
padding: 0;
}
canvas {
display: block;
}
<script src="https://cdn.jsdelivr.net/npm/p5@1.4.0/lib/p5.js"></script>
我想围绕椭圆旋转文本。每个字符都会出现一些轻微的变形,但椭圆形变得越极端(即越不完美的圆圈)。理想情况下,此方法可以在任何高宽比的椭圆形甚至其他形状(例如圆角矩形)上很好地呈现文本。
到目前为止我的方法是:
- 找到椭圆的边
- 使用三角函数围绕扭曲的椭圆旋转一个点
- 在这个位置画
text()
- 根据它的位置旋转这个
text()
- 通过
for
循环执行此操作,遍历提供的字符串中的每个字符。
字符获得奇怪的间距(即使使用 textWidth
)和奇怪的旋转(即使动态计算)。您可以在下面的代码片段中看到这一点,特别是 'spinning' 的 'nn' 和 'piece' 的 'ie'。
let canvasWidth = 400;
let canvasHeight = 400;
let spinSpeed = 0.25;
let ellipseWidth = 280;
let ellipseHeight = 200;
let angle = 0;
let sourceText = "A beautiful piece of spinning text. ";
let sourceCharacters = sourceText.toUpperCase().split("");
function setup() {
createCanvas(canvasWidth, canvasHeight);
angleMode(DEGREES);
textSize(18);
textAlign(LEFT, BASELINE);
}
function draw() {
background(0);
// Draw an ellipse
stroke("rgba(255, 255, 255, 0.15)");
noFill();
ellipse(canvasWidth / 2, canvasHeight / 2, ellipseWidth, ellipseHeight);
// Prepare for operations around circle
translate(canvasWidth / 2, canvasHeight / 2);
// Create a revolving angle
if (angle < 360) {
angle += spinSpeed;
} else {
angle = 0;
}
// Set variables for trigonometry
let widthR = ellipseWidth / 2;
let heightR = ellipseHeight / 2;
let dx = widthR * cos(angle);
let dy = heightR * sin(angle);
// Set variable for offsetting each character
let currentOffset = 0;
// Loop through each chracter and place on oval edge
for (let i = 0; i < sourceCharacters.length; i++) {
push();
dx = widthR * cos(angle + currentOffset);
dy = heightR * sin(angle + currentOffset);
translate(dx, dy);
rotate(angle + currentOffset + 90);
stroke("rgba(255, 255, 255, 0.25)");
rect(0, 0, textWidth(sourceCharacters[i]), 10);
fill("white");
text(sourceCharacters[i], 0, 0);
currentOffset += textWidth(sourceCharacters[i]);
pop();
}
}
html,
body {
margin: 0;
padding: 0;
}
canvas {
display: block;
}
<script src="https://cdn.jsdelivr.net/npm/p5@1.4.0/lib/p5.js"></script>
我怀疑字符之间的这种奇怪间距不是由于代码错误造成的。这与您无法制作非常好的地球地图的原因非常简单。在 p5js 中,椭圆和圆并不完美。
在椭圆的某些点,构成像素的圆内的线在某些点比其他点长。我不确定如何解释得好,但我能解释的最好方法是使用与电子游戏中 strafing
相同的概念。
Credits to Dan Violet Sagmiller (Game Developer)
在视频游戏中,游戏开发人员有时会在玩家同时向前和向侧面(对角线)移动时降低玩家的速度。这是因为一些复杂的数学表明扫射会使玩家移动得更快。
扫射发生的唯一原因是游戏开发者改变玩家移动的方式。他们没有使用带向量的三角函数或旋转数学,而是使用转换逻辑 (+, -)。这通常会提高游戏性能。根据您的代码,您还使用了转换逻辑:
translate(dx, dy);
rotate(angle + currentOffset + 90);
基本上,当每个字母在程序的每一帧中移动到其新位置时,由于扫射的工作方式,一些字母比其他字母移动得更远。
我没有任何解决方案,只有降低效果的方法,您可以通过以下方式看到:
减小和增大尺寸;您可以增加椭圆的大小或减小文本的大小和长度。
保持椭圆的比例彼此相似;通过保持宽度和高度彼此接近,字母之间存在间距的效果将会降低。
我希望这能解决您的问题,或者至少有助于减少它的影响。祝你有美好的一天!
这行代码没有意义,可能只是巧合:
currentOffset += textWidth(sourceCharacters[i]);
这是使用以像素为单位的字符宽度作为以度为单位的角度(基于调用 rotate
/sin
/cos
.
正确的实现方式包括在绘制每个字符的点处找到椭圆的切线,使用该切线的斜率来确定每个字符的角度,然后确定旋转的度数space 下一个字母必须使字形边界框的角接触。找到那个字母间距角度似乎很难精确地做到,但我有一个应该工作得相当好的 hack。
let canvasWidth = 400;
let canvasHeight = 400;
let spinSpeed = 0.25;
let ellipseWidth = 280;
let ellipseHeight = 200;
let angle = 0;
let sourceText = "A beautiful piece of spinning text. ";
let sourceCharacters = sourceText.toUpperCase().split("");
function setup() {
createCanvas(canvasWidth, canvasHeight);
angleMode(DEGREES);
textSize(18);
// drawing each letter from it's center point makes determining the angle and
// the spacing a little easier I think.
textAlign(CENTER, BASELINE);
}
function draw() {
background(0);
// Draw an ellipse
stroke("rgba(255, 255, 255, 0.15)");
noFill();
ellipse(canvasWidth / 2, canvasHeight / 2, ellipseWidth, ellipseHeight);
// Prepare for operations around circle
translate(canvasWidth / 2, canvasHeight / 2);
// Create a revolving angle
if (angle < 360) {
angle += spinSpeed;
} else {
angle = 0;
}
// Set variables for trigonometry
const widthR = ellipseWidth / 2;
const heightR = ellipseHeight / 2;
let dx, dy;
// Set variable for offsetting each character
let currentOffset = 0;
// Loop through each chracter and place on oval edge
for (let i = 0; i < sourceCharacters.length; i++) {
push();
/* This isn't the best way to convert an angle to a position on an ellipse
* It causes distortions depending on the tightness of the curve.
dx = widthR * cos(angle + currentOffset);
dy = heightR * sin(angle + currentOffset); */
// This give a more accurate position. See: https://math.stackexchange.com/a/2258243/771335
let r = widthR * heightR / sqrt(widthR ** 2 * sin(angle + currentOffset) ** 2 + heightR ** 2 * cos(angle + currentOffset) ** 2);
dx = r * cos(angle + currentOffset);
dy = r * sin(angle + currentOffset);
translate(dx, dy);
// This is the derivative of the equation for an ellipse.
// Calculating the derivative at X gives us the current slope.
let tangent = -1 * (heightR ** 2) * dx / (widthR ** 2 * sqrt(heightR ** 2 * (widthR ** 2 - dx ** 2) / widthR ** 2));
if (dy < 0) {
tangent *= -1;
}
// Use the tangent slope to determine rotation angle
let rotation = atan2(tangent, 1);
if (dy > 0) {
rotation += 180;
}
rotate(rotation);
let charWidth = textWidth(sourceCharacters[i]);
stroke("rgba(255, 255, 255, 0.25)");
rect(-charWidth / 2, 0, charWidth, -18);
fill("white");
text(sourceCharacters[i], 0, 0);
// find the angle between the vector to the center of the current letter
// and the vector to the center of the next letter projected onto the
// current tangent line. This is a bit of a hack.
if (i + 1 < sourceCharacters.length) {
let spacing = (charWidth + textWidth(sourceCharacters[i + 1])) / 2;
let vCur = createVector(dx, dy);
let vNext =
vCur.copy().add(
createVector(spacing * cos(rotation), spacing * sin(rotation))
);
currentOffset += vCur.angleBetween(vNext);
}
pop();
}
}
html,
body {
margin: 0;
padding: 0;
}
canvas {
display: block;
}
<script src="https://cdn.jsdelivr.net/npm/p5@1.4.0/lib/p5.js"></script>