Простейший 3d двиг средствами delphi. Тор
tor, 3d двиг на delphi

Итак, господа, что мы сегодня будем делать?
Мы попытаемся реализовать свой простой 3d движок и на радостях построить такой замечательный объект как тор (в простонародии бублик).

Готовый проект и немного extra примеров:
скачать
скачать
скачать

Будем работать без перспективы, чисто черчение, мы не художники. Перспективу, впрочем, потом несложно добавить — всего-то рассчитать расстояние до точки и домножить на коэффициент.

[[часть 1]]

[средства]
Мы будем использовать компонент TImage и массив Pixels в Canvas. И больше ничего, никаких компонентов ; )

[приготовления #1]
Напишем вспомогательные функции, которые будут нам давать центр изображения. Нам он не раз понадобится, и это более чем удобно.

function cx(im:TImage):integer;
begin
  result:=im.ClientWidth div 2;
end;

function cy(im:TImage):integer;
begin
  result:=im.ClientHeight div 2;
end;

[понятия]
Введем некоторые понятия для облегчения наших действий: оригинал — точка в пространстве, в котором мы будем представлять наш объект, проекция — точка на поверхности картинки. Чуть позже напишем функции для преобразования оригинала в проекцию и отображения.

[типы]
Какие типы нам понадобятся? Для начала точка в пространстве:

type
  TOriginal=record
    x:real;
    y:real;
    z:real;
  end;

Все расчеты будем проводить с типом real.
Для проекции используем уже существующий тип [b]TPoint[/b] с целочисленными значениями x и y. При преобразовании и будет происходить округление.
Вспомним, что мы собираемся рисовать в изометрии, но их же много! Мы будем использовать прямоугольную изометрию(гуглим), но сделаем всё по уму, чтобы мы всегда могли поменять изометрию на диметрию, проекцию на проецирующую плоскость и т.д.
Вводим следующий тип:

type
  TAxonometry=record
    xkx:real;
    xky:real;
    ykx:real;
    yky:real;
    zkx:real;
    zky:real;
  end;

Что же это, собственно, такое? Это просто-напросто коэффициенты, на которые мы домножаем координаты оригинала и получаем координаты проекции. Об этом ниже.

[константы]
И константочка для изометрии:

const
  Izometry:TAxonometry=(xkx:-0.866{cos(pi/6)};
                        xky:0.5{sin(pi/6)};
                        ykx:0.866{-cos(pi/6)};
                        yky:0.5{sin(pi/6)};
                        zkx:0;
                        zky:-1;);

Будем использовать правую тройку осей с осью Z, направленной вверх.
В комментариях указаны формулы, приближенные значения которых мы видим, их достаточно легко получить. Кстати, это очень прикольный способ задания констант типа record.

[приготовления #2]
По горячим следам напишем функцию для преобразования оригинала:

function Original2Point(v:TOriginal;ax:TAxonometry;im:TImage):TPoint;
var
  p:TPoint;
begin
  with ax do
    p.X:=Round(cx(im)+xkx*v.x+ykx*v.y+zkx*v.z);
  with ax do
    p.Y:=Round(cy(im)+xky*v.x+yky*v.y+zky*v.z);

  Result:=p;
end;

Что же получается? Да, каждая координата оригинала вносит вклад в обе координаты проекции, который берем из передаваемого объекта типа TAxonometry. В нашем случае вклад координаты Z оригинала в координату X равен нулю, а в координату Y минус единице. Ибо ось Z направлена вертикально, как сказано выше, от изменения координаты по ней, проекция не сдвигается ни влево, ни вправо, а по оси Y в коспоненте TImage нумерация идёт сверху, поэтому переворачиваем. В остальных случаях оси пространства оригиналов проецируются как прямые под углом 30 градусов к горизонту, см. константу Izometry.

И рисовальня:

procedure OriginalDraw(v:TOriginal;ax:TAxonometry;c:TColor;im:TImage);
var
  p:TPoint;
begin
  p:=Original2Point(v,ax,im);
  im.Canvas.Pixels[p.X,p.Y]:=c;
end;

Тут объяснять не надо, разве что оговорюсь, что для рисования тора мы её использовать не будем, мы построим каркас из линий. Эта функция необходима для рисования ГМТ или хрен-знает-каких-тел, у которых ничего неизвестно про непрерывность.

Функции, аналогичные MoveTo и LineTo:

procedure OriginalMoveTo(v:TOriginal;ax:TAxonometry;im:TImage);
var
  p:TPoint;
begin
  p:=Original2Point(v,ax,im);
  im.Canvas.MoveTo(p.X,p.Y);
end;

procedure OriginalLineTo(v:TOriginal;ax:TAxonometry;c:TColor;im:TImage);
var
  p:TPoint;
begin
  p:=Original2Point(v,ax,im);
  im.Canvas.Pen.Color:=c;
  im.Canvas.LineTo(p.X,p.Y);
end;

С их помощью и будет сделан наш тор. Чуть позже всё рассчитаем.

[подитог]
Ну что, чувачки? Ожидали чего-то посложней? Простое домножение на коэффициент и у нас есть простейший 3d двиг. Надо сходить поесть и займемся тором. Напоминаю, что в оригинале эта статья опубликована на http://parsers.info

[[часть 2]]
[матчасть]
Строить тор будем основываясь на параметрических уравнениях:
x(i,k) = (R + r cos i) cos k
y(i,k) = (R + r cos i) sin k
z(i,k) = r sin i
где пераметры i,k варьируются от 0 до 2pi

Из констант нам надо объявить шаги параметров для циклов рисования горизонально и вертикально расположенных окружностей, мы их будем рисовать отдельно. Шаг для вертикальных окружностей должен быть больше, для ощущения равномерности.

[пишем процедуру]
1) Начальный этап. Объявим локальные переменные и константы.

procedure DrawTor(r1,r2:real;ax:TAxonometry;c:TColor;im:TImage);
var
  i,k:integer;
  ir,kr:real;
  curr:TOriginal;
const
  steph:real=28.8;
  stepv:real=3.6;
begin

i, k — параметры в градусах, ir, kr — переменные, подготовленные для перевода в радианы. curr — расчетная точка, steph и stepv — шаги, про которые написано чуть повыше. Число 360 должно делиться на шаги без остатка!
2) Рисуем горизонтальные окружности.

  for i:=0 to Round(360/steph) do begin
    ir:=DegToRad(i * steph);
    curr.x:=(r1 + r2 * cos(ir)) * cos(0);
    curr.y:=(r1 + r2 * cos(ir)) * sin(0);
    curr.z:=r2 * sin(ir);
    OriginalMoveTo(curr,ax,im);
    for k:=0 to 360 do begin
      kr:=DegToRad(k);
      curr.x:=(r1 + r2 * cos(ir)) * cos(kr);
      curr.y:=(r1 + r2 * cos(ir)) * sin(kr);
      curr.z:=r2 * sin(ir);
      OriginalLineTo(curr,ax,c,im);
    end; // for
  end; // for

Здесь в первом цикле перед началом вложенного рассчитывается «нулевая» точка — точка, с которой начинаем рисование окружности, т.е. ломаной линии из 360 отрезков.
3) Дорисовываем вертикальные окружности.

  for i:=0 to Round(360/stepv) do begin
    ir:=DegToRad(i * stepv);
    curr.x:=(r1 + r2 * cos(0)) * cos(ir);
    curr.y:=(r1 + r2 * cos(0)) * sin(ir);
    curr.z:=r2 * sin(0);
    OriginalMoveTo(curr,ax,im);
    for k:=0 to 360 do begin
      kr:=DegToRad(k);
      curr.x:=(r1 + r2 * cos(kr)) * cos(ir);
      curr.y:=(r1 + r2 * cos(kr)) * sin(ir);
      curr.z:=r2 * sin(kr);
      OriginalLineTo(curr,ax,c,im);
    end; // for
  end; // for

Здесь меняем местами параметры и еще кое-что, по аналогии.
4) Финал.
На форму ставим компонент TImage, свойство Align ставим alClient, чтобы занимало всё пространство.
На событие создания формы пишем:

  DrawTor(200,40,Izometry,clRed,imgOut);

>>>the end.<<< [вывод]
В результате наших изысканий мы написали простейшие функции преобразования координат и, используя параметрические уравнения, нарисовали тор. Это всего лишь образец того, что можно сделать. Конечно до современных игр на псп далеко (а то ещё придётся обращаться в сервисный центр psp), но все движки с нуля начинали с чего-то такого.

(c) crystalbit, http://parsers.info
допускается копирование при сохранении копирайта