我想围绕椭圆旋转文本。每个字符都会发生一些轻微的变形,但是椭圆变得越极端(即越不完美的圆)。理想情况下,此方法可以在任意高度和宽度比例的椭圆形,甚至圆角矩形等其他形状上很好地呈现文本。
到目前为止,我的方法是:
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>
答案 0 :(得分:1)
据我怀疑,字符之间的这种奇怪的间距不是由于代码错误造成的。这很简单,原因与您无法制作出非常好的地球地图的原因相同。在 p5js 中,椭圆和圆并不完美。
在椭圆的某些点上,构成像素的圆内的线在某些点上比其他点长。我不知道如何很好地解释它,但我能解释它的最好方法是使用与视频游戏中的 strafing
相同的概念。
Credits to Dan Violet Sagmiller (Game Developer)
在视频游戏中,游戏开发者有时会降低玩家同时向前和向侧面移动(对角线)的速度。这是因为一些复杂的数学表明扫射会导致玩家移动得更快。
发生扫射的唯一原因是游戏开发者改变玩家移动的方式。他们不使用三角函数或旋转数学与向量,而是使用转换逻辑(+,-)。这通常会提高游戏的性能。根据您的代码,您还使用了转换逻辑:
translate(dx, dy);
rotate(angle + currentOffset + 90);
本质上,当每个字母在程序的每一帧中移动到新位置时,由于扫射的工作方式,有些字母会比其他字母移动得更远。
我没有任何解决方案,只有通过以下方式减少影响的方法:
减小和增大尺寸;您可以增加椭圆的大小或减少文本的大小和长度。
保持椭圆的比例相似;通过保持宽度和高度彼此接近,字母间距的影响会降低。
我希望这能解决您的问题,或者至少有助于减少它的影响。祝你有美好的一天!
答案 1 :(得分:1)
这行代码没有意义,可能只是巧合:
currentOffset += textWidth(sourceCharacters[i]);
这是使用以像素为单位的字符宽度作为以度为单位的角度(基于在调用 rotate
/sin
/cos
时使用 currentOffset。
正确的实现包括在您绘制每个字符的点处找到椭圆的切线,使用该切线的斜率来确定每个字符的角度,然后确定所需的旋转度数隔开下一个字母,使字形边界框的角接触。找到那个字母间距角度似乎很难精确地完成,但我有一个应该可以很好地工作的技巧。
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>