Сетка перспективы HTML5
Я пытался создать перспективную сетку на холсте и изменил функцию с другого веб-сайта с таким результатом:
function keystoneAndDisplayImage(ctx, img, x, y, pixelHeight, scalingFactor) {
var h = img.height,
w = img.width,
numSlices = Math.abs(pixelHeight),
sliceHeight = h / numSlices,
polarity = (pixelHeight > 0) ? 1 : -1,
heightScale = Math.abs(pixelHeight) / h,
widthScale = (1 - scalingFactor) / numSlices;
for(var n = 0; n < numSlices; n++) {
var sy = sliceHeight * n,
sx = 0,
sHeight = sliceHeight,
sWidth = w;
var dy = y + (sliceHeight * n * heightScale * polarity),
dx = x + ((w * widthScale * n) / 2),
dHeight = sliceHeight * heightScale,
dWidth = w * (1 - (widthScale * n));
ctx.drawImage(img, sx, sy, sWidth, sHeight,
dx, dy, dWidth, dHeight);
}
}
Это создает почти хорошую перспективную сетку, но она не масштабирует высоту, поэтому каждый квадрат имеет одинаковую высоту. Вот рабочий jsFiddle и как он должен выглядеть, чуть ниже холста. Я не могу придумать математическую формулу, которая искажала бы высоту пропорционально "расстоянию в перспективе" (вверху). Я надеюсь, вы понимаете. Извините за языковые ошибки.
Любая помощь будет принята с благодарностью
С уважением1 ответ
К сожалению, нет правильного способа, кроме использования 3D-подхода. Но, к счастью, это не так сложно.
Следующее создаст сетку, которая может вращаться по оси X (как на вашей картинке), поэтому нам нужно сосредоточиться только на этой оси.
Чтобы понять, что происходит: мы определяем сетку в декартовом координатном пространстве. Причудливое слово, означающее, что мы определяем наши точки как векторы, а не как абсолютные координаты. То есть одна ячейка сетки может перейти от 0,0 до 1,1 вместо, например, от 10,20 до 45, 45, чтобы просто взять несколько чисел.
На этапе проецирования мы проецируем эти декартовы координаты на наши экранные координаты.
Результат будет таким:
Хорошо, давайте углубимся в это - сначала мы настроим некоторые переменные, которые нам нужны для проекции и т. Д.:
fov = 512, /// Field of view kind of the lense, smaller values = spheric
viewDist = 22, /// view distance, higher values = further away
w = ez.width / 2, /// center of screen
h = ez.height / 2,
angle = -27, /// grid angle
i, p1, p2, /// counter and two points (corners)
grid = 10; /// grid size in Cartesian
Чтобы настроить сетку, мы не корректируем петли (см. Ниже), а изменяем fov
а также viewDist
а также изменение grid
увеличить или уменьшить количество клеток.
Допустим, вы хотите более экстремальный вид - установив fov
до 128 и viewDist
до 5 вы получите этот результат, используя тот же grid
а также angle
:
"Волшебная" функция, выполняющая всю математику, выглядит следующим образом:
function rotateX(x, y) {
var rd, ca, sa, ry, rz, f;
rd = angle * Math.PI / 180; /// convert angle into radians
ca = Math.cos(rd);
sa = Math.sin(rd);
ry = y * ca; /// convert y value as we are rotating
rz = y * sa; /// only around x. Z will also change
/// Project the new coords into screen coords
f = fov / (viewDist + rz);
x = x * f + w;
y = ry * f + h;
return [x, y];
}
И это все. Стоит отметить, что именно сочетание новых Y и Z делает линии меньше в верхней части (под этим углом).
Теперь мы можем создать сетку в декартовом пространстве следующим образом и повернуть эти точки непосредственно в координатное пространство экрана:
/// create vertical lines
for(i = -grid; i <= grid; i++) {
p1 = rotateX(i, -grid);
p2 = rotateX(i, grid);
ez.strokeLine(p1[0], p1[1], p2[0], p2[1]); //from easyCanvasJS, see demo
}
/// create horizontal lines
for(i = -grid; i <= grid; i++) {
p1 = rotateX(-grid, i);
p2 = rotateX(grid, i);
ez.strokeLine(p1[0], p1[1], p2[0], p2[1]);
}
Также обратите внимание, что позиция 0,0 является центром экрана. Вот почему мы используем отрицательные значения, чтобы выйти на левую сторону или вверх. Вы можете видеть, что две центральные линии - это прямые линии.
И это все, что нужно сделать. Чтобы покрасить ячейку, вы просто выбираете декартову координату, а затем конвертируете ее, вызывая rotateX()
и у вас будут координаты, необходимые для углов.
Например, выбирается случайный номер ячейки (от -10 до 10 по осям X и Y):
c1 = rotateX(cx, cy); /// upper left corner
c2 = rotateX(cx + 1, cy); /// upper right corner
c3 = rotateX(cx + 1, cy + 1); /// bottom right corner
c4 = rotateX(cx, cy + 1); /// bottom left corner
/// draw a polygon between the points
ctx.beginPath();
ctx.moveTo(c1[0], c1[1]);
ctx.lineTo(c2[0], c2[1]);
ctx.lineTo(c3[0], c3[1]);
ctx.lineTo(c4[0], c4[1]);
ctx.closePath();
/// fill the polygon
ctx.fillStyle = 'rgb(200,0,0)';
ctx.fill();
Анимированная версия, которая может помочь увидеть, что происходит.