Вечер 25.04.2026. Сорок лет Во вложении - пятак РБМК на самопальном OpenGL-движке Ходить - стрелками, летать - PgUp/PgDn, крутить объект - WSAD, крутить камеру - кнопки мыши и колесо, колесо можно крутить и нажимать F1 для справки Bonus0: то же самое на VBA для Excel. Тот случай, когда пишешь на ассемблере, но это VBA Bonus1: то же самое на С. Перевод на С с ассемблера
ОБАЛДЕТЬ ! ! ! ! ! ! Вот это проект, так проект ! ! ! ! Можно сказать, что ПРОЕКТИЩЕ !!!!! Я аж прослезился от радости за автора этого проекта - от БЕЛОЙ ЗАВИСТИ ! ! ! :=) P.S. По-моему, в шапку нужно добавить, что есть проект для MASM x64 ! ! ! !
Чтобы не менять название темы, я добавил движок под 32 бита. По идее, должен запускаться на всём, что имеет SSE, т.е. PIII Katmai и новее. Летает на Compaq Presario 1700T с Celeron 700E, 128Мб RAM и ATI Rage Mobility-M1. Дописал пока не всё. Как и в случае с "Москвичом", упёрся в парсер. Я не могу определиться, что я хочу от этого парсера, поэтому не могу решить несформулированную задачу. Что касается геометрии, то тут всё в порядке. Я не использую штатные glTranslatef и glRotatef. Всё своими ручками. Матрицу Projection я настраиваю на перспективу и больше не трогаю. Матрицу ModelView не преобразую вообще. Просто загружаю в неё результаты обработки двух кастомных матриц. А кастомные матрицы - это разделённые Model и View, только я их для различия назвал ObjectVolatile и CameraVolatile соответственно. Разделение позволяет независимо преобразовывать координаты модели и координаты камеры - К.О. Плюс для быстрого сброса я добавил ещё две матрицы: ObjectDefault и CameraDefault. Их я сразу проинициализировал в сегменте .code значениями, которые подбирал весь предыдущий год. Вот так мы ходим на SSE (матрица Camera): Спойлер: Перенос со сменой базиса Код (ASM): ;dx_local = dStep * mtxCameraVolatile(0i) ;dy_local = dStep * mtxCameraVolatile(1i) ;dz_local = dStep * mtxCameraVolatile(2i) mulss xmm0,dword ptr[eax+0*4] mulss xmm1,dword ptr[eax+4*4] mulss xmm2,dword ptr[eax+8*4] ;Fill all 4 xmm lanes shufps xmm0,xmm0,0 ;dx_local shufps xmm1,xmm1,0 ;dy_local shufps xmm2,xmm2,0 ;dz_local ;Accumulate world-space displacement ;dx contributes to world via column 0 ;dy contributes to world via column 1 ;dz contributes to world via column 2 ;dx_world = dx_local*m[00] + dy_local*m[04] + dz_local*m[08] ;dy_world = dx_local*m[01] + dy_local*m[05] + dz_local*m[09] ;dz_world = dx_local*m[02] + dy_local*m[06] + dz_local*m[10] movaps xmm3,oword ptr[ecx+0*4] ;old[00..03] movaps xmm4,oword ptr[ecx+4*4] ;old[04..07] movaps xmm5,oword ptr[ecx+8*4] ;old[08..11] mulps xmm0,xmm3 mulps xmm1,xmm4 mulps xmm2,xmm5 addps xmm0,xmm1 addps xmm0,xmm2 ;Add to the Translation Column ;x_world = x_world + dx_world ;y_world = y_world + dy_world ;z_world = z_world + dz_world movaps xmm6,oword ptr[ecx+12*4] ;old[12..15] addps xmm0,xmm6 Вот так мы поворачиваем модель на SSE (матрица Object): Спойлер: Поворот в другом базисе Код (ASM): Rx: ;1 0 0 0 ;0 cos -sin 0 ;0 sin cos 0 ;0 0 0 1 ;Load Values movaps xmm2,oword ptr[ecx+4*4] ;old[04..07] movaps xmm3,oword ptr[ecx+8*4] ;old[08..11] movaps xmm4,xmm2 ;old[04..07] movaps xmm5,xmm3 ;old[08..11] ;new[04] = c * old[04] + s * old[08] ;new[05] = c * old[05] + s * old[09] ;new[06] = c * old[06] + s * old[10] ;new[07] = c * old[07] + s * old[11] mulps xmm2,xmm1 ;old[04..07]*cos mulps xmm3,xmm0 ;old[08..11]*sin addps xmm2,xmm3 movaps oword ptr[ecx+4*4],xmm2 ;new[04..07] ;new[08] = c * old[08] - s * old[04] ;new[09] = c * old[09] - s * old[05] ;new[10] = c * old[10] - s * old[06] ;new[11] = c * old[11] - s * old[07] mulps xmm5,xmm1 ;old[08..11]*cos mulps xmm4,xmm0 ;old[04..07]*sin subps xmm5,xmm4 movaps oword ptr[ecx+8*4],xmm5 ;new[08..11] jmp ModelRotate_End Ry: ; cos 0 sin 0 ; 0 1 0 0 ;-sin 0 cos 0 ; 0 0 0 1 ;Load Values movaps xmm2,oword ptr[ecx+0*4] ;old[00..03] movaps xmm3,oword ptr[ecx+8*4] ;old[08..11] movaps xmm4,xmm2 ;old[00..03] movaps xmm5,xmm3 ;old[08..11] ;new[00] = c * old[00] - s * old[08] ;new[01] = c * old[01] - s * old[09] ;new[02] = c * old[02] - s * old[10] ;new[03] = c * old[03] - s * old[11] mulps xmm2,xmm1 ;old[00..03]*cos mulps xmm3,xmm0 ;old[08..11]*sin subps xmm2,xmm3 movaps oword ptr[ecx+0*4],xmm2 ;new[00..03] ;new[08] = s * old[00] + c * old[08] ;new[09] = s * old[01] + c * old[09] ;new[10] = s * old[02] + c * old[10] ;new[11] = s * old[03] + c * old[11] mulps xmm4,xmm0 ;old[00..03]*sin mulps xmm5,xmm1 ;old[08..11]*cos addps xmm4,xmm5 movaps oword ptr[ecx+8*4],xmm4 ;new[08..11] jmp ModelRotate_End Rz: ;cos -sin 0 0 ;sin cos 0 0 ; 0 0 1 0 ; 0 0 0 1 ;Load Values movaps xmm2,oword ptr[ecx+0*4] ;old[00..03] movaps xmm3,oword ptr[ecx+4*4] ;old[04..07] movaps xmm4,xmm2 ;old[00..03] movaps xmm5,xmm3 ;old[04..07] ;new[00] = c * old[00] + s * old[04] ;new[01] = c * old[01] + s * old[05] ;new[02] = c * old[02] + s * old[06] ;new[03] = c * old[03] + s * old[07] mulps xmm2,xmm1 ;old[00..03]*cos mulps xmm3,xmm0 ;old[04..07]*sin addps xmm2,xmm3 movaps oword ptr[ecx+0*4],xmm2 ;new[00..03] ;new[04] = c * old[04] - s * old[00] ;new[05] = c * old[05] - s * old[01] ;new[06] = c * old[06] - s * old[02] ;new[07] = c * old[07] - s * old[03] mulps xmm4,xmm0 ;old[00..03]*sin mulps xmm5,xmm1 ;old[04..07]*cos subps xmm5,xmm4 movaps oword ptr[ecx+4*4],xmm5 ;new[04..07] Управление WSAD (поворот модели) + стрелки, PgUp, PgDn (движение камеры) + shift (ускорение)
ml64, спасибо вам от всех любителей ассемблеров ! :=) К сожалению, ваш EXE-файл у меня выдаёт не то, что нужно, а сам я пока не компилировал, нужно будет MASM32 на досуге установить.
Так это и есть то, что нужно! Там просто болванка - белый прямоугольник, на нём можно проверить работу всех матриц. Я же говорю: будет парсер - будет графика, а сейчас пока красоту надо искать в чистоте решений. Это же поэзия: Код (ASM): ;2.1. Activate the ModelView Matrix push 1700h ;GL_MODELVIEW call glMatrixMode ;2.2. Load the current Camera matrix push offset mtxCameraVolatile call glLoadMatrixf ;2.3. Multiply by the current Object matrix push offset mtxObjectVolatile call glMultMatrixf Вместо всех конских преобразований в матрицах Projection и ModelView ...Для того, чтобы повернуть матрицу в другом базисе, нужно всего лишь 12 инструкций. А на современных процессорах - ещё меньше
- А, понятно. - А у вас нет такого-же, но с парсером, без крыльев и с перламутровыми пуговицами? - Нет, да. Ну, будем искать. Будем искать.
Обновление с полным функционалом мыши. Экранные координаты проецируются на сферу. По просьбам коллег поздние версии SSE и AVX не используются. Только SSE1, только OpenGL1.0, только хардкор: Код (ASM): ArcballRotation proc ;Computes the arcball rotation ;from two unit vectors (vecOld and vecNew) ;on the virtual sphere, expressed in Camera's Local Coordinates ;and produces a 3x3 rotation matrix in WORLD coordinates ;stored in mtxArcball. ;Assumes the camera's 3x3 block (columns 0,1,2 of mtxCameraVolatile) ;is orthonormal, so its transpose is its inverse. ;Returns True on success, False on degenerate (skip rotation). ;1. Read the two unit vectors movaps xmm0,oword ptr[xVectorOld] movaps xmm1,oword ptr[xVectorNew] ;2. Cross product vecCam = vecOld * vecNew ;xCam = y0*z1 - z0*y1 ;yCam = z0*x1 - x0*z1 ;zCam = x0*y1 - y0*x1 movaps xmm2,xmm0 ;vecOld shufps xmm2,xmm0,11001001b movaps xmm3,xmm1 ;vecNew shufps xmm3,xmm1,11010010b movaps xmm4,xmm0 ;vecOld shufps xmm4,xmm0,11010010b movaps xmm5,xmm1 ;vecNew shufps xmm5,xmm1,11001001b mulps xmm2,xmm3 mulps xmm4,xmm5 subps xmm2,xmm4 ;xmm2 = vecCam ;3. Dot product cosA = old * new ;cosA = x0*x1 + y0*y1 + z0*z1 movaps xmm3,xmm0 ;vecOld mulps xmm3,xmm1 ;vecNew movaps xmm4,xmm3 shufps xmm4,xmm4,00111001b ;rotate right addss xmm3,xmm4 shufps xmm4,xmm4,00111001b ;rotate right addss xmm3,xmm4 ;xmm3 = cosA shufps xmm3,xmm3,0 ;broadcast cosA ;4. Magnitude of the cross product ;sinA = |vecCam| = Sqr(xCam*xCam + yCam*yCam + zCam*zCam) movaps xmm4,xmm2 ;vecCam mulps xmm4,xmm4 movaps xmm5,xmm4 shufps xmm5,xmm5,00111001b ;rotate right addss xmm4,xmm5 shufps xmm5,xmm5,00111001b ;rotate right addss xmm4,xmm5 sqrtss xmm4,xmm4 ;xmm4 = sinA shufps xmm4,xmm4,0 ;broadcast sinA ;5. Degenerate case ;If sinA is too small, the rotation is ;either identity (cosA > 0) or 180 deg (cosA < 0) ;The latter has no defined axis from the cross product. ;Skip both for now. comiss xmm4,eps ;Compare against epsilon jb Degenerate ;6. Normalize the axis ;xCam = xCam / sinA ;yCam = yCam / sinA ;zCam = zCam / sinA divps xmm2,xmm4 ;7. Transform axis from CAMERA-LOCAL to WORLD ;vecWorld = M_camera_3x3_transpose * vecCam ;In memory (column-major), mtxCameraVolatile's columns are: ;[m00 m04 m08] ;[m01 m05 m09] ;[m02 m06 m10] lea eax,mtxCameraVolatile ;Its transpose multiplied by (xCam, yCam, zCam) is: ;xWorld = m00*xCam + m01*yCam + m02*zCam ;yWorld = m04*xCam + m05*yCam + m06*zCam ;zWorld = m08*xCam + m09*yCam + m10*zCam movaps xmm5,xmm2 movaps xmm6,xmm2 movaps xmm7,xmm2 mulps xmm5,oword ptr[eax+00*4] ;m[00..03] * xCam mulps xmm6,oword ptr[eax+04*4] ;m[04..07] * yCam mulps xmm7,oword ptr[eax+08*4] ;m[08..11] * zCam movaps xmm0,xmm5 ;wx[0] movaps xmm1,xmm5 shufps xmm1,xmm1,00111001b ;rotate right addss xmm0,xmm1 shufps xmm1,xmm1,00111001b ;rotate right addss xmm0,xmm1 movss xmm5,xmm0 ;xWorld movaps xmm0,xmm6 ;wy[0] movaps xmm1,xmm6 shufps xmm1,xmm1,00111001b ;rotate right addss xmm0,xmm1 shufps xmm1,xmm1,00111001b ;rotate right addss xmm0,xmm1 movss xmm6,xmm0 ;yWorld movaps xmm0,xmm7 ;wz[0] movaps xmm1,xmm7 shufps xmm1,xmm1,00111001b ;rotate right addss xmm0,xmm1 shufps xmm1,xmm1,00111001b ;rotate right addss xmm0,xmm1 movss xmm7,xmm0 ;zWorld ;8. Build the Rodrigues 3x3 rotation matrix (world space) ;c = cosA = xmm3 ;s = sinA = xmm4 ;t = 1 - cosA movss xmm2,posOne subss xmm2,xmm3 ;xmm2 = t shufps xmm2,xmm2,0 ;broadcast t ;9. Compute Rodrigues and store Store in mtxArcball (column-major) ;[m00 m04 m08] <- [r00 r01 r02] ;[m01 m05 m09] <- [r10 r11 r12] ;[m02 m06 m10] <- [r20 r21 r22] lea ecx,mtxArcball ;So memory layout is: ;mtxArcball(00) = R00 = t*wx*wx + c ;mtxArcball(01) = R10 = t*wx*wy + s*wz ; ;mtxArcball(02) = R20 = t*wx*wz - s*wy ;mtxArcball(03) = 0 ;mtxArcball(04) = R01 = t*wx*wy - s*wz ; ;mtxArcball(05) = R11 = t*wy*wy + c ;mtxArcball(06) = R21 = t*wy*wz + s*wx ;mtxArcball(07) = 0 ;mtxArcball(08) = R02 = t*wx*wz + s*wy ;mtxArcball(09) = R12 = t*wy*wz - s*wx ;mtxArcball(10) = R22 = t*wz*wz + c ;mtxArcball(11) = 0 ;mtxArcball(12) = 0 ;mtxArcball(13) = 0 ;mtxArcball(14) = 0 ;mtxArcball(15) = 1 movss xmm0,xmm2 ;t mulss xmm0,xmm5 ;t*wx movss dword ptr[ecx+00*4],xmm0 ;t*wx movss dword ptr[ecx+02*4],xmm0 ;t*wx mulss xmm0,xmm6 ;t*wx*wy movss dword ptr[ecx+01*4],xmm0 ;t*wx*wy movss xmm1,xmm4 ;s mulss xmm1,xmm7 ;s*wz subss xmm0,xmm1 ;t*wx*wy - s*wz movss dword ptr[ecx+04*4],xmm0 ;Done addss xmm1,dword ptr[ecx+01*4] ;t*wx*wy + s*wz movss dword ptr[ecx+01*4],xmm1 ;Done movss xmm0,dword ptr[ecx+02*4] ;t*wx mulss xmm0,xmm7 ;t*wx*wz movss dword ptr[ecx+08*4],xmm0 ;t*wx*wz movss xmm1,xmm4 ;s mulss xmm1,xmm6 ;s*wy subss xmm0,xmm1 ;t*wx*wz - s*wy movss dword ptr[ecx+02*4],xmm0 ;Done addss xmm1,dword ptr[ecx+08*4] ;t*wx*wz + s*wy movss dword ptr[ecx+08*4],xmm1 ;Done movss xmm0,xmm5 ;wx mulss xmm0,dword ptr[ecx+00*4] ;t*wx*wx addss xmm0,xmm3 ;t*wx*wx + c movss dword ptr[ecx+00*4],xmm0 ;Done movss xmm0,xmm2 ;t mulss xmm0,xmm6 ;t*wy movss dword ptr[ecx+05*4],xmm0 ;t*wy mulss xmm0,xmm7 ;t*wy*wz movss dword ptr[ecx+06*4],xmm0 ;t*wy*wz movss xmm1,xmm4 ;s mulss xmm1,xmm5 ;s*wx subss xmm0,xmm1 ;t*wy*wz - s*wx movss dword ptr[ecx+09*4],xmm0 ;Done addss xmm1,dword ptr[ecx+06*4] ;t*wy*wz + s*wx movss dword ptr[ecx+06*4],xmm1 ;Done movss xmm0,xmm6 ;wy mulss xmm0,dword ptr[ecx+05*4] ;t*wy*wy addss xmm0,xmm3 ;t*wy*wy + c movss dword ptr[ecx+05*4],xmm0 ;Done movss xmm0,xmm2 ;t mulss xmm0,xmm7 ;t*wz mulss xmm0,xmm7 ;t*wz*wz addss xmm0,xmm3 ;t*wz*wz + c movss dword ptr[ecx+10*4],xmm0 ;Done mov dword ptr[ecx+07*4],0 mov dword ptr[ecx+11*4],0 mov dword ptr[ecx+12*4],0 mov dword ptr[ecx+13*4],0 mov dword ptr[ecx+14*4],0 movss xmm0,posOne movss dword ptr[ecx+15*4],xmm0 jmp Success ;Failure Degenerate: invoke WriteLog,offset szErrArcball mov eax,-1 ret ;Report Success Success: xor eax,eax ret ArcballRotation endp
Вчера перемудрил с формулой Родрига, сегодня поравил. Получилось красиво, уместился в 6 регистров. Код (ASM): ;10. Compute Rodrigues and store Store in mtxArcball (column-major) ;[m00 m04 m08] <- [r00 r01 r02] ;[m01 m05 m09] <- [r10 r11 r12] ;[m02 m06 m10] <- [r20 r21 r22] lea ecx,mtxArcball ;So memory layout is: ;mtxArcball(00) = R00 = t*xw*xw+cosA ;mtxArcball(01) = R10 = t*xw*yw+sinA*zw ;mtxArcball(02) = R20 = t*xw*zw-sinA*yw ;mtxArcball(03) = 0 ;mtxArcball(04) = R01 = t*yw*xw-sinA*zw ;mtxArcball(05) = R11 = t*yw*yw+cosA ;mtxArcball(06) = R21 = t*yw*zw+sinA*xw ;mtxArcball(07) = 0 ;mtxArcball(08) = R02 = t*zw*xw+sinA*yw ;mtxArcball(09) = R12 = t*zw*yw-sinA*xw ;mtxArcball(10) = R22 = t*zw*zw+cosA ;mtxArcball(11) = 0 ;mtxArcball(12) = 0 ;mtxArcball(13) = 0 ;mtxArcball(14) = 0 ;mtxArcball(15) = 1 ;Initial state: ;xmm0 = 0,zw,yw,xw ;xmm1 = free (junk) ;xmm2 = t,t,t,t ;xmm3 = cosA,cosA,cosA,cosA ;xmm4 = sinA,sinA,sinA,sinA ;xmm5 = xw,xw,xw,xw ;xmm6 = yw,yw,yw,yw ;xmm7 = zw,zw,zw,zw ;Comment style: little-endian mulps xmm0,xmm2 ;0,t*zw,t*yw,t*xw movaps xmm1,xmm0 ;0,t*zw,t*yw,t*xw movaps xmm2,xmm0 ;0,t*zw,t*yw,t*xw mulps xmm0,xmm5 ;0,t*zw*xw,t*yw*xw,t*xw*xw mulps xmm1,xmm6 ;0,t*zw*yw,t*yw*yw,t*xw*yw mulps xmm2,xmm7 ;0,t*zw*zw,t*yw*zw,t*xw*zw mulss xmm5,xmm4 ;?,?,?,sinA*xw mulss xmm6,xmm4 ;?,?,?,sinA*yw mulss xmm7,xmm4 ;?,?,?,sinA*zw addss xmm0,xmm3 ;0,t*zw*xw,t*yw*xw,t*xw*xw+cosA movss dword ptr[ecx+00*4],xmm0 shufps xmm0,xmm0,00111001b ;a00,0,t*zw*xw,t*yw*xw subss xmm0,xmm7 ;a00,0,t*zw*xw,t*yw*xw-sinA*zw movss dword ptr[ecx+04*4],xmm0 shufps xmm0,xmm0,00111001b ;a04,a00,0,t*zw*xw addss xmm0,xmm6 ;a04,a00,0,t*zw*xw+sinA*yw movss dword ptr[ecx+08*4],xmm0 addss xmm1,xmm7 ;0,t*zw*yw,t*yw*yw,t*xw*yw+sinA*zw movss dword ptr[ecx+01*4],xmm1 shufps xmm1,xmm1,00111001b ;a01,0,t*zw*yw,t*yw*yw addss xmm1,xmm3 ;a01,0,t*zw*yw,t*yw*yw+cosA movss dword ptr[ecx+05*4],xmm1 shufps xmm1,xmm1,00111001b ;a05,a01,t*zw*yw subss xmm1,xmm5 ;a05,a01,t*zw*yw-sinA*xw movss dword ptr[ecx+09*4],xmm1 subss xmm2,xmm6 ;0,t*zw*zw,t*yw*zw,t*xw*zw-sinA*yw movss dword ptr[ecx+02*4],xmm2 shufps xmm2,xmm2,00111001b ;a02,0,t*zw*zw,t*yw*zw addss xmm2,xmm5 ;a02,0,t*zw*zw,t*yw*zw+sinA*xw movss dword ptr[ecx+06*4],xmm2 shufps xmm2,xmm2,00111001b ;a06,a02,0,t*zw*zw addss xmm2,xmm3 ;a06,a02,0,t*zw*zw+cosA movss dword ptr[ecx+10*4],xmm2
А я, вообще, по-моему, вчера свою какую-то формулу изобрёл. Так и не понял даже какую, я же не математик. Назвал эту формулу, на всякий случай "Гипотеза Пуанкаре - отдыхает". По-моему, получилось даже чуть покрасивее, чем у ml64. Регистры не считал. Что их считать - все равно все умрём, а регистры останутся.
Пошла жара! Мне очень нравится. Залип минут на десять. --- Сообщение объединено, 27 сен 2026 --- Однозначно! И отлично крутится на Celeron 700E
Не всё так однозначно. Идея-то однозначно ваша и движок ваш. Если бы не вы, мне бы и в голову подобное не пришло. Осталось теперь в 64-х битную версию сконвертировать, да может кое-какие ошибки устранить. У меня в логах, при управлении с клавиатуры и с мыши, накапливаются ошибки, но это по-моему, с других камер (или как их правильно назвать?) не связанных с моими цилиндрами. Может там половину можно отключить ?
Там трекбол даёт такую погрешность, что я его вообще заменю на последовательные повороты в плоскостях. Сижу, вычищаю --- Сообщение объединено, 27 сен 2026 --- Заменил трекбол на обычные повороты в плоскостях. Проще, точнее. Хотя не так эффектно.
Переписал конвертер float -> string под x32 Оживил статусную строку Вернул счётчик кадров (шестнадцатеричный) Добавил счётчик FPS --- Сообщение объединено, 27 сен 2026 --- Выдаёт более 90 FPS, если я правильно считаю --- Сообщение объединено, 27 сен 2026 --- 101 FPS под Windows Me:
А Windows 98 там у вас нет случайно? А то у родителей где-то в кладовке стоит Пентиум-3 1 Ггц. c 512 Мб. памяти и Win 98. Папа выбрасывать не даёт. Могу попросить, чтобы испытал.
??? Pentium III 1 ГГц - это легенда. Берегите его! У меня был PIII Katmai-550. Я его в 2022 году отнёс на помойку. По семейным обстоятельствам. Теперь жалею. Памяти там было 128, и это в 2001 году считалось очень много, а у Вас 512. Если видеокарта ещё MX460, то это просто эталонный артефакт. Если верить бенчмаркам тех лет, то машины на последних Coppermine были быстрее, чем Willamette и Northwood. --- Сообщение объединено, 27 сен 2026 --- Давайте доработаем Вашу идею: разберёмся с нормалями и добавим свет. Будет круто!
Давайте. А что для этого нужно ? Я, только, если честно, в графике слабоват. Мне эту идею с цилиндрами сосед, студент посоветовал. Он на играх специализируется. Так что где-то сосед, где-то у ИИ что-то спросишь, где-то в гугле что-то поищешь... В результате имеем то, что имеем... Но мне понравилось...
Договориться об интерфейсах. Чтобы Вы были в комфорте с изменениями движка. Т.е. я меняю (условно) алгоритм поворота стрелками, а Ваш код остаётся в своём модуле. Сейчас так и есть. Где-то вызывающие функции по максимуму высвободить. И всё будет взаимно независимо. И портируемо (пригодно для х64 или Вулкана, например)
ml64, в общем, можете брать эти вращающиеся цилиндры и делать с ними всё, что считаете нужным. Чувствуется, что у вас к этому делу более профессиональный подход, чем у меня. Я всё-таки в графике и движках OPENGL - любитель, а может даже и делитант. Если что-то будет нужно от меня - напишете в этот тред или в личку, чем смогу - постараюсь помочь.