Разбираемся с технологией 3D программирования

Автор Kadet, 23 июня 2021, 09:16

0 Пользователи и 1 гость просматривают эту тему.

mikekaganski

Цитата: Kadet от 23 июля 2021, 12:23У меня сцена состоит из десятков вложенных в неё моделей. Ведь матрицы пересчитывать нужно по каждому объекту.

Почему? Я бы ожидал, что можно применить преобразование к сцене целиком. Или к сгруппированным объектам как к целому.
С уважением,
Михаил Каганский

Kadet

#46
Цитата: mikekaganski от 23 июля 2021, 12:31Почему? Я бы ожидал, что можно применить преобразование к сцене целиком. Или к сгруппированным объектам как к целому.
Ну, я так и применяю, только без пересчёта матриц. Меняю положения камеры камер по vrp, а матрица сама пересчитывается, силами, заложенными LO в сцену.

Немного разобрался в проблеме.
Нужно расстояние и фокус ставить побольше, тогда и вращение будет почти нормальным.
xScene3D.D3DSceneDistance = 1000000
xScene3D.D3DSceneFocalLength = 1000000

Хотя и тут есть свои подводные камни. Проходя точку, где Z=0, вся сцена инвертируется, а уже на следующем шаге всё нормализуется.
Попробую ещё отодвинуть сцену. И если не поможет - сделаю проскок опасного момента Z=0.

P.S.: оказалось не всё так просто.

mikekaganski

Цитата: mikekaganski от 23 июля 2021, 11:55(у Вас обычно столько всего там, что не разберёшься)

А, ну как обычно: примеры не подготовлены, скрипт падает на

LoadLib("ANLib", GlobalScope.BasicLibraries)

из-за "Sub-procedure or function procedure not defined."
С уважением,
Михаил Каганский

Kadet

#48
Удалял это подключение к библиотекам и даже проверял. Скачал и снова проверил.
Подключение осталось только в макросе DrawLathe3D который в данном примере не используется.
Эти две строчки можно просто удалить или заремить.
Не пойму, почему у вас выкинуло, ведь этот макрос не используется.
Убрал. Попробуйте.

Попробовал преобразовать 3D матрицу сцены к виду вращения по оси Y.

cos(ф)   0  sin(ф)
  0     1    0
-sin(ф)  0  cos(ф)

Практически ничего не изменилось.

В общем, при повороте более +/-70град фигуру просто выворачивает наизнанку и переворачивает вверх ногами. Фигура представляется без заливки, только каркасы.

Матрица преобразования (xScene3D.D3DTransformMatrix) по-умолчанию настроена на перемещение, т.е. - единицы по диагонали матрицы.
Хотелось бы покрутить сцену, но придётся ставить ограничители на 70град.

mikekaganski

Он используется в примере, который Вы в Багзиллу положили, и здесь в тесте 11. Начиная с теста 12 работает, но те, кто попытаются Вам помочь там, наткнутся на проблему.
С уважением,
Михаил Каганский

Kadet


Kadet

#51
Продолжу свой дневник.
Таки победил я вращение сцены. Как и предлагал Михаил испробовал не коррекцию камеры по X и Z, а изменение матрицы (xScene3D.D3DTransformMatrix) по формулам вращения по оси Y.
cos(ф)   0  sin(ф)
  0     1    0
-sin(ф)  0  cos(ф)
Всё получилось. Хотя, крутит только до 180град. Дальше начинает скакать - шаг после 180 град, шаг назад (до 180 град), шаг туда, шаг сюда. Видимо потому, что cos(ф) преображается в минус. Но исправлять уже не стал. Оставлю, как заведомый баг.

Однако, первоначальное смещение местоположения камеры оставил (то, которое настраивается до занесения фигур, при создании самой сцены). Так лучше получается, чем выводить с помощью матрицы:
D3DCamera = xScene3D.D3DCameraGeometry
D3DCamera.vrp.PositionX = 41000
D3DCamera.vrp.PositionY = 11000
D3DCamera.vpn.DirectionX = 0
D3DCamera.vpn.DirectionY = 0
xScene3D.D3DCameraGeometry = D3DCamera


А так же, вращение по оси X (вверх/вниз), тоже оставил изменением положения камеры. Так тоже получается лучше. При использовании матрицы сцена ведёт себя как-то неадекватно. Видимо это связано с тем, что приходится налагать матрицу по X на матрицей по Y, и тут получается недоработка. Но углубляться не стал, потому что никому это не нужно. Вряд ли кому-нибудь понадобится полный вид сверху на объект. Получится, оставил лишь сектор для просмотра сверху/снизу.

В итоге макрос вращения получился таким. (Тригонометрические функции загоняю в calc, потому что в Basic они урезаны и есть не все). Направление вращения определяется именами кнопок, на которые происходит клик мыши (sName)
'*** ПОВОРОТ СЦЕНЫ 3D ***************************************
Sub Scena3DRotate(oEvent)
Dim xDoc As object, xPage As object, xScene3D As object, oSheet As object, Matrix
Dim index%, sName$, sTag$, Xpoz&, Ypoz&, Zpoz&, Xsm&, Ysm&, Zsm&, R#, alfa#, alf#, Al#, X, iRad#
Dim aPosition As New com.sun.star.awt.Point
Dim TheSize As New com.sun.star.awt.Size
Dim aPosition3D As New com.sun.star.drawing.Position3D
Dim aDirection3D As New com.sun.star.drawing.Direction3D
Dim aDirection3D1 As New com.sun.star.drawing.Direction3D
Dim D3DCamera As New com.sun.star.drawing.CameraGeometry
'********************************************************
xDoc = oEvent.Source.getModel().Parent.Parent.Parent
xPage = oEvent.Source.getModel().Parent.Parent
sName = oEvent.Source.getModel().Name
index = oEvent.Source.getModel().Tag
'********************************************************
xPage = xDoc.DrawPages(index)
oSheet = xDoc.Sheets(index)
xScene3D = SerchScene3D(xPage, oSheet.Name)
'********************************************************
oSheet.getCellByPosition(0, 1000).setFormula("=ROUNDUP(A1000;0)")
oSheet.getCellByPosition(0, 1002).setFormula("=ASIN(A1002)")
oSheet.getCellByPosition(0, 1004).setFormula("=ATAN(A1004)")
oSheet.getCellByPosition(0, 1006).setFormula("=TAN(A1006)")
oSheet.getCellByPosition(0, 1008).setFormula("=SIN(A1008)")
oSheet.getCellByPosition(0, 1010).setFormula("=COS(A1010)")
oSheet.getCellByPosition(0, 1012).setFormula("=ACOS(A1012)")
'********************************************************
aPosition = xScene3D.getPosition()
TheSize = xScene3D.getSize()
'********************************************************
D3DCamera = xScene3D.D3DCameraGeometry
Xpoz = D3DCamera.vrp.PositionX
Ypoz = D3DCamera.vrp.PositionY
Zpoz = D3DCamera.vrp.PositionZ
'********************************************************
aDirection3D = D3DCamera.vpn
'********************************************************
iRad = 0.01745
Al = 10*iRad
'********************************************************
Matrix = xScene3D.D3DTransformMatrix
'******************
a = Matrix.Line1.Column1
b = Matrix.Line1.Column2
c = Matrix.Line1.Column3
S1 = Matrix.Line1.Column4
'******************
d = Matrix.Line2.Column1
e = Matrix.Line2.Column2
f = Matrix.Line2.Column3
S2 = Matrix.Line2.Column4
'******************
h = Matrix.Line3.Column1
i = Matrix.Line3.Column2
j = Matrix.Line3.Column3
S3 = Matrix.Line3.Column4
'******************
Dx = Matrix.Line4.Column1
Dy = Matrix.Line4.Column2
Dz = Matrix.Line4.Column3
S4 = Matrix.Line4.Column4
'********************************************************
Select Case sName
Case "Left"
'********************************************************
If a=1 Then
alf = 0
Else
oSheet.getCellByPosition(0, 1011).setValue(a)
alf = oSheet.getCellByPosition(0, 1012).getValue()
End If
'******************
alfa = alf + Al
oSheet.getCellByPosition(0, 1007).setValue(alfa) 'SIN
oSheet.getCellByPosition(0, 1009).setValue(alfa) 'COS
iSin = oSheet.getCellByPosition(0, 1008).getValue()
iCos = oSheet.getCellByPosition(0, 1010).getValue()
'******************
a = iCos
c = -iSin
'******************
h = iSin
j = iCos
'********************************************************
Case "Xcentr"
'********************************************************
D3DCamera.vrp.PositionX = 41000
D3DCamera.vrp.PositionY = 11000
D3DCamera.vrp.PositionZ = 102886
'******************
a = 1
b = 0
c = 0
S1 = 0
'******************
d = 0
e = 1
f = 0
S2 = 0
'******************
h = 0
i = 0
j = 1
S3 = 0
'******************
Dx = 0
Dy = 0
Dz = 0
S4 = 1
'********************************************************
Case "Right"
'********************************************************
If a=1 Then
alf = 0
Else
oSheet.getCellByPosition(0, 1011).setValue(a)
alf = oSheet.getCellByPosition(0, 1012).getValue()
End If
'******************
alfa = alf - Al
oSheet.getCellByPosition(0, 1007).setValue(alfa) 'SIN
oSheet.getCellByPosition(0, 1009).setValue(alfa) 'COS
iSin = oSheet.getCellByPosition(0, 1008).getValue()
iCos = oSheet.getCellByPosition(0, 1010).getValue()
'******************
a = iCos
c = -iSin
'******************
h = iSin
j = j+iCos
'********************************************************
Case "Up"
D3DCamera.vrp.PositionY = D3DCamera.vrp.PositionY+2000
Case "Ycentr"
'********************************************************
D3DCamera.vrp.PositionX = 41000
D3DCamera.vrp.PositionY = 11000
D3DCamera.vrp.PositionZ = 102886
'******************
a = 1
b = 0
c = 0
S1 = 0
'******************
d = 0
e = 1
f = 0
S2 = 0
'******************
h = 0
i = 0
j = 1
S3 = 0
'******************
Dx = 0
Dy = 0
Dz = 0
S4 = 1
'********************************************************
Case "Down"
D3DCamera.vrp.PositionY = D3DCamera.vrp.PositionY-2000
End Select
'********************************************************
Matrix.Line1.Column1 = a
Matrix.Line1.Column2 = b
Matrix.Line1.Column3 = c
Matrix.Line1.Column4 = S1
'******************
Matrix.Line2.Column1 = d
Matrix.Line2.Column2 = e
Matrix.Line2.Column3 = f
Matrix.Line2.Column4 = S2
'******************
Matrix.Line3.Column1 = h
Matrix.Line3.Column2 = i
Matrix.Line3.Column3 = j
Matrix.Line3.Column4 = S3
'******************
Matrix.Line4.Column1 = Dx
Matrix.Line4.Column2 = Dy
Matrix.Line4.Column3 = Dz
Matrix.Line4.Column4 = S4
'******************
xScene3D.D3DTransformMatrix = Matrix
'********************************************************
xScene3D.D3DCameraGeometry = D3DCamera
'********************************************************
xScene3D.setPosition(aPosition)
xScene3D.setSize(TheSize)
'********************************************************
End Sub

И ещё есть проблемы с вращением против часовой стрелки и тоже связано с тригонометрией. Однако, она со временем решится. И, те, кто будет использовать мои функции, думаю, тоже будут в состоянии решить эту проблему. Подумать и разобраться всегда полезно.

Kadet

Ошибка, скачки на границах 180 гр. и 0 гр. происходят потому, что косинусы, к примеру, 170 гр. и 190 гр., как и 10гр. и 350 гр. имеют одно и то же значение. А я получаю предыдущий угол, к которому прибавляю шаг угла, по арккосинусу из матрицы. Вот и получается путаница у программы.
Нужно выдумать другой ход.

Kadet

#53
Исправил баг вращения через манипуляции с acos и asin.
oSheet.getCellByPosition(0, 1001).setValue(h)
aSin = oSheet.getCellByPosition(0, 1002).getValue()
If aSin<0 Then aSin = 3.14159265358979-aSin
oSheet.getCellByPosition(0, 1011).setValue(a)
aCos = oSheet.getCellByPosition(0, 1012).getValue()
If aCos>=aSin Then
alf = aCos
Else
alf = aSin
End If


Так же решил задачу с позиционирование цилиндров в сцене. Снова с помощью матрицы преобразования, только теперь непосредственно самой модели,а не сцены - (xShape3D.D3DTransformMatrix). Позиция  в системе координат модели вращения, как и любой другой, выставляется последним столбцом матрицы преобразования, соответственно: Sx, Sy, Sz.
a    b    c   Sx
d    e    f   Sy
h    i    j   Sz
Dx   Dy   Dz  S


В общем, получилось как в примере (вложение). Макросы при нём.

mikekaganski

#54
Цитата: Kadet от 27 июля 2021, 06:29
If aSin<0 Then aSin = 3.14159265358979-aSin

Pi - это предопределённая константа[/b].

Цитата: Kadet от 27 июля 2021, 06:29
Исправил баг вращения через манипуляции с acos и asin.

Вообще следовало бы использовать ATAN2.

Цитата: Kadet от 26 июля 2021, 21:30Тригонометрические функции загоняю в calc, потому что в Basic они урезаны и есть не все

Класть данные в ячейки таблицы, формулы в другие ячейки, и считывать значения оттуда - очень некрасивый хак. Есть специальный метод вызова функций Calc из Basic, так создайте соответствующие MySin, MyArcCos и т.п.

Графические объекты имеют свойство Anchor. Этот якорь указывает на диапазон, а у диапазона есть свойство Spreadsheet, у которого, в свою очередь, есть свойство DrawPage (в Вашем случае Anchor указывает непосредственно на Spreadsheet, но для общности это неважно). Поэтому совсем необязательно передавать везде и объект, и DrawPage. И использование имени вида '"Сцена3D_" & sName' - ужасно нечитабельно. Почему не передавать объект прямо?

Sub DrawScene3D(xDoc as object, xPage as object, index%, sName$, Optional FillColor, Optional Transparence, Optional iLayer)
...
xPage.add(xScene3D)
...
Camera3D(xDoc, index)
...
End Sub
...
Sub Camera3D(xDoc, index%)
...
xPage = xDoc.DrawPages(index)
...


Избыточность интерфейсов - просто зашкаливает. Передавать в DrawScene3D и DrawPage, и индекс, который можно использовать для получения DrawPage - это зачем? Фактически можно передавать в Camera3D одну только сцену (вместо документа и индекса), и из неё получать и лист, и DrawPage (если было бы нужно), и не использовать этот "SerchScene3D".

Кстати, Camera3D вызывает Scena3DSize, поэтому явные вызовы последней после Camera3D избыточны. (В принципе всё нужно обрамить отключением прорисовки для скорости, но я полагаю, что Вы это не сделали для наблюдения за процессом и отладки?)

Sub Scena3DRotate(oEvent)
...
Dim D3DCamera As New com.sun.star.drawing.CameraGeometry
...
D3DCamera = xScene3D.D3DCameraGeometry


Зачем создавать новый объект (используя as new), если Вы собираетесь тут же переназначить объект?
С уважением,
Михаил Каганский

mikekaganski

Я разбирался-разбирался, и внезапно осознал, что Ваш дом стоит на плоскости XZ, а "верх" его направлен в направлении оси Y. Это за что вы так бедного читателя?
С уважением,
Михаил Каганский

rami

Цитата: mikekaganski от 27 июля 2021, 17:47
Я разбирался-разбирался, и внезапно осознал, что Ваш дом стоит на плоскости XZ, а "верх" его направлен в направлении оси Y. Это за что вы так бедного читателя?
Он художник, он так видит O0

Там какая-то колдунская перспектива. Если развернуть объект кнопкой "стрелка влево" в положение как на картинке, то можно заметить, что наблюдатель должен находиться правее линии A-B-C, следовательно столбик "C" будет самым ближним к наблюдателю, а столбик "D" самым далёким из ближнего ряда. Визуально должно быть чем дальше (в данном случае левее), тем короче и тоньше должны выглядеть элементы одного типа и размера, а получается наоборот. Столбики дальнего ряда выглядят короче и тоньше столбиков ближнего ряда, это правильно.

Kadet

#57
Спасибо за подсказки и критику. Учту.

Действительно, домик стоит на плоскости XZ, а верх его по оси Y. Однако, не пойму, что тут удивительного.
В конструировании зданий есть такое понятие - ТЛП (теоретическая линия пола), а если быть точней - плоскость пола. Её всегда используют как "0". Именно поэтому этот "0" я и кладу на плоскость XZ. Здание выше, фундамент ниже (хотя в примере я фундамент тоже поднимаю вверх... по недосмотру, должно быть наоборот).

Перспектива действительно немного корявая. Но это делаю не я, а заморочки LO. Я лишь смещаю камеру обзора на X=41000, Y=1100. В все элелементы строго по координатам - полширины влево (-), полширины вправо (+) по Ox от X=0, на приближении полдлины по Z - это фасад. И т.д. А все перспективы и виды делаю только с помощью камеры и сцены.
Пытался сделать повороты с помощью матрицы сцены. Чёто мне не понравилось.Так и то лучше, чем по матрице.

На счёт столбиков - аналогичная история. Я закладываю одни и те же размеры по всему периметру. Меняю лишь расположение. По Oz - полдлины конструкции к себе (+) или полдлины от себя (-) относительно точки Z=0. А толщину и прочие размеры предлагает сама LO.

Kadet

По поводу передачи индексов и страниц. Всё не так просто.
В данном примере эскиз состоит из одной страницы, и, соответственно, одной DrowPage. И как следствие - из одной сцены на этой странице.
Разрабатываемый мною проект состоит из как минимум 3-х страниц с тремя своими сценами. И к этим сценам идут обращения попеременно из одного и того же макроса. Допустим, я работаю со страницей "Каркас", ставлю столбы. А параллельно в других страницах идёт отображение - в стр. "Фундамент" параллельно создаются сваи фундамента на каждый столб, а в странице "Общее" параллельно создаётся каркас, который в последствии будет обшиваться. Именно поэтому я принял решение оперировать индексами. Просто я не беру эти индексы как следствие, а через них управляю процессом.

Kadet

По поводу тригонометрии - многого не знаю в Basic относительно этого, но не умею сам создавать ту же функцию ACOS, которой нет в Basic.
Про вызовы функций калк не знал. Спасибо! Буду пользовать, ибо эта необходимость у меня постоянна. То же управляемое округление лучше делать в калке чем в басике.