Загрузка данных
#include <windows.h>
#include <GL/glut.h>
#include <GL/glu.h>
#include <cmath>
#include <vector>
#include <algorithm>
// ---------- Глобальные переменные ----------
const double PI = 3.14159265358979323846;
int winW = 800, winH = 600;
// Параметры поверхности
int nLon = 80; // разбиение по долготе
int nLat = 60; // разбиение по широте
double R = 1.5; // базовый радиус
// Углы проекции
double alpha = 30.0 * PI / 180.0; // поворот вокруг Z
double beta = 20.0 * PI / 180.0; // наклон камеры
bool perspective = false; // режим проекции
double cameraDist = 8.0; // дистанция для перспективы
// Режимы отображения
bool wireframe = false; // каркас / заливка
bool useTexture = false; // текстура вкл/выкл
GLuint textureId = 0; // ID текстуры
// Освещение (позиция, компоненты)
GLfloat lightPos[] = { 5.0f, 5.0f, 5.0f, 1.0f };
GLfloat ambientLight[] = { 0.25f, 0.25f, 0.25f, 1.0f };
GLfloat diffuseLight[] = { 0.9f, 0.9f, 0.9f, 1.0f };
GLfloat specularLight[] = { 1.0f, 1.0f, 1.0f, 1.0f };
// Материал
GLfloat matAmbient[] = { 0.1f, 0.15f, 0.35f, 1.0f };
GLfloat matDiffuse[] = { 0.2f, 0.3f, 0.8f, 1.0f };
GLfloat matSpecular[] = { 0.7f, 0.7f, 0.7f, 1.0f };
GLfloat matShininess = 80.0f;
// Z-буфер
std::vector<float> zBuffer;
int zbufW = 0, zbufH = 0;
// ---------- Генерация текстуры в памяти (процедурная) ----------
void generateTexture() {
const int texSize = 256;
GLubyte* texData = new GLubyte[texSize * texSize * 3];
for (int i = 0; i < texSize; i++) {
for (int j = 0; j < texSize; j++) {
int idx = (i * texSize + j) * 3;
// Шахматный паттерн с цветовым градиентом
int check = ((i / 16) + (j / 16)) % 2;
texData[idx] = check ? 100 + (i % 32) * 4 : 200 - (i % 32) * 2;
texData[idx + 1] = check ? 150 + (j % 32) * 3 : 100 + (j % 32) * 3;
texData[idx + 2] = check ? 200 - (i % 32) * 2 : 150 + (i % 32) * 4;
}
}
glGenTextures(1, &textureId);
glBindTexture(GL_TEXTURE_2D, textureId);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, texSize, texSize, 0,
GL_RGB, GL_UNSIGNED_BYTE, texData);
delete[] texData;
}
// ---------- Преобразование: мировые → экранные координаты ----------
void worldToScreen(double xw, double yw, double zw,
double &xs, double &ys, double &zs) {
// Поворот вокруг оси Z (alpha)
double x1 = xw * cos(alpha) - yw * sin(alpha);
double y1 = xw * sin(alpha) + yw * cos(alpha);
double z1 = zw;
// Поворот вокруг оси X (beta)
double y2 = y1 * cos(beta) - z1 * sin(beta);
double z2 = y1 * sin(beta) + z1 * cos(beta);
double x2 = x1;
if (!perspective) {
// Аксонометрия
xs = x2; ys = y2; zs = z2;
} else {
// Перспектива
double d = cameraDist - z2;
if (d < 0.01) d = 0.01;
xs = x2 * cameraDist / d;
ys = y2 * cameraDist / d;
zs = z2;
}
}
// ---------- Параметризация поверхности с новым сдвигом ----------
// Формула: "Сдвиг пропорционален квадрату широты"
// x = x_ш + R·(B/45)², y = y_ш, z = 2·z_ш
void dropSurface(double B, double L, double &x, double &y, double &z) {
// B ∈ [-π/2, +π/2] — широта (радианы), L ∈ [0, 2π] — долгота
double cosB = cos(B);
double sinB = sin(B);
// Базовая форма "капли"
double radiusFactor = (1.0 - sinB) * 0.5 + 0.05; // 1.05 → 0.05
double r = R * cosB * radiusFactor;
// Координаты до сдвига (индекс "ш" — "широтные")
double x_sh = r * sin(L);
double y_sh = r * cos(L);
double z_sh = R * sinB * (1.0 + 0.8 * (sinB + 1.0) * 0.5);
// === ПРИМЕНЕНИЕ СДВИГА ПО ФОРМУЛЕ ===
double B_deg = B * 180.0 / PI; // радианы → градусы
double shift = R * (B_deg / 45.0) * (B_deg / 45.0); // (B/45)²
x = x_sh + shift; // сдвиг по X пропорционален B²
y = y_sh; // Y без изменений
z = 2.0 * z_sh; // масштабирование по Z
}
// ---------- Инициализация Z-буфера ----------
void initZBuffer(int w, int h) {
zbufW = w; zbufH = h;
zBuffer.assign(w * h, -1e30f); // минимальное значение глубины
}
// ---------- Вывод пикселя с тестом глубины ----------
void putPixel(int x, int y, float z, float r, float g, float b) {
if (x < 0 || x >= zbufW || y < 0 || y >= zbufH) return;
int idx = y * zbufW + x;
if (z > zBuffer[idx]) { // Z-тест
zBuffer[idx] = z;
glColor3f(r, g, b);
glBegin(GL_POINTS);
glVertex2i(x, y);
glEnd();
}
}
// ---------- Модель освещения Фонга ----------
void computeLight(double xw, double yw, double zw,
double nx, double ny, double nz,
float &r, float &g, float &b) {
// Нормализация нормали
double len = sqrt(nx*nx + ny*ny + nz*nz);
if (len < 1e-9) { r=g=b=0.5f; return; }
nx /= len; ny /= len; nz /= len;
// Вектор к источнику света
double lx = lightPos[0] - xw;
double ly = lightPos[1] - yw;
double lz = lightPos[2] - zw;
double llen = sqrt(lx*lx + ly*ly + lz*lz);
if (llen < 1e-9) llen = 1.0;
lx /= llen; ly /= llen; lz /= llen;
// Диффузная составляющая
double diff = nx*lx + ny*ly + nz*lz;
if (diff < 0.0) diff = 0.0;
// Зеркальная составляющая (halfway vector)
double hx = lx, hy = ly, hz = lz + 1.0; // направление к камере ~ (0,0,1)
double hlen = sqrt(hx*hx + hy*hy + hz*hz);
hx /= hlen; hy /= hlen; hz /= hlen;
double spec = nx*hx + ny*hy + nz*hz;
if (spec < 0.0) spec = 0.0;
spec = pow(spec, matShininess);
// Итоговый цвет
r = matAmbient[0] * ambientLight[0] +
matDiffuse[0] * diffuseLight[0] * diff +
matSpecular[0] * specularLight[0] * spec;
g = matAmbient[1] * ambientLight[1] +
matDiffuse[1] * diffuseLight[1] * diff +
matSpecular[1] * specularLight[1] * spec;
b = matAmbient[2] * ambientLight[2] +
matDiffuse[2] * diffuseLight[2] * diff +
matSpecular[2] * specularLight[2] * spec;
// Ограничение диапазона [0,1]
r = (r > 1.0f) ? 1.0f : r;
g = (g > 1.0f) ? 1.0f : g;
b = (b > 1.0f) ? 1.0f : b;
}
// ---------- Текстурные координаты ----------
void computeTexture(double B, double L, float &u, float &v) {
u = (L + PI) / (2.0 * PI); // долгота → [0,1]
v = (B + PI/2.0) / PI; // широта → [0,1]
}
// ---------- Основная отрисовка ----------
void drawSurface() {
double dB = PI / nLat;
double dL = 2.0 * PI / nLon;
// Включение текстуры при необходимости
if (useTexture && textureId) {
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, textureId);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
}
for (int i = 0; i < nLat; i++) {
double B1 = -PI/2.0 + i * dB;
double B2 = B1 + dB;
for (int j = 0; j < nLon; j++) {
double L1 = j * dL;
double L2 = L1 + dL;
// 4 вершины квада в мировых координатах
double xw[4], yw[4], zw[4];
dropSurface(B1, L1, xw[0], yw[0], zw[0]);
dropSurface(B2, L1, xw[1], yw[1], zw[1]);
dropSurface(B2, L2, xw[2], yw[2], zw[2]);
dropSurface(B1, L2, xw[3], yw[3], zw[3]);
// Проекция в экранные координаты
double xs[4], ys[4], zs[4];
for (int k = 0; k < 4; k++)
worldToScreen(xw[k], yw[k], zw[k], xs[k], ys[k], zs[k]);
// Преобразование в пиксели
int px[4], py[4];
double scale = zbufW / 5.0;
for (int k = 0; k < 4; k++) {
px[k] = (int)((xs[k] + 2.5) * scale);
py[k] = (int)((2.5 - ys[k]) * (zbufH / 5.0));
}
// Нормаль к грани (векторное произведение)
double ux = xw[1]-xw[0], uy = yw[1]-yw[0], uz = zw[1]-zw[0];
double vx = xw[3]-xw[0], vy = yw[3]-yw[0], vz = zw[3]-zw[0];
double nx = uy*vz - uz*vy;
double ny = uz*vx - ux*vz;
double nz = ux*vy - uy*vx;
// Back-face culling (только для заливки)
double camX = sin(alpha)*sin(beta);
double camY = cos(alpha)*sin(beta);
double camZ = cos(beta);
if (!wireframe && (nx*camX + ny*camY + nz*camZ < 0)) continue;
// Расчёт освещения (центр грани)
float cr = 1.0f, cg = 1.0f, cb = 1.0f;
if (!wireframe) {
double cx = (xw[0]+xw[1]+xw[2]+xw[3]) / 4.0;
double cy = (yw[0]+yw[1]+yw[2]+yw[3]) / 4.0;
double cz = (zw[0]+zw[1]+zw[2]+zw[3]) / 4.0;
computeLight(cx, cy, cz, nx, ny, nz, cr, cg, cb);
}
// Текстурные координаты для углов
float tu[4] = {0}, tv[4] = {0};
if (useTexture) {
computeTexture(B1, L1, tu[0], tv[0]);
computeTexture(B2, L1, tu[1], tv[1]);
computeTexture(B2, L2, tu[2], tv[2]);
computeTexture(B1, L2, tu[3], tv[3]);
}
// Разбиение квада на 2 треугольника
for (int t = 0; t < 2; t++) {
int idx[3] = {0, 1 + t, 2 + t};
// === КАРКАСНЫЙ РЕЖИМ ===
if (wireframe) {
glColor3f(cr, cg, cb);
glBegin(GL_LINE_LOOP);
for (int k = 0; k < 3; k++)
glVertex2i(px[idx[k]], py[idx[k]]);
glEnd();
continue;
}
// === РЕЖИМ ЗАЛИВКИ С Z-БУФЕРОМ ===
// Сортировка вершин по Y для сканирующей растеризации
if (py[idx[0]] > py[idx[1]]) std::swap(idx[0], idx[1]);
if (py[idx[0]] > py[idx[2]]) std::swap(idx[0], idx[2]);
if (py[idx[1]] > py[idx[2]]) std::swap(idx[1], idx[2]);
int yStart = std::max(0, py[idx[0]]);
int yEnd = std::min(zbufH - 1, py[idx[2]]);
for (int y = yStart; y <= yEnd; y++) {
float xLeft = 1e9f, xRight = -1e9f;
float zLeft = 0, zRight = 0;
float uLeft = 0, uRight = 0, vLeft = 0, vRight = 0;
// Поиск пересечений рёбер с текущей строкой
for (int e = 0; e < 3; e++) {
int v0 = idx[e], v1 = idx[(e+1)%3];
if ((py[v0] <= y && py[v1] > y) || (py[v1] <= y && py[v0] > y)) {
float tEdge = float(y - py[v0]) / (py[v1] - py[v0]);
float xInter = px[v0] + tEdge * (px[v1] - px[v0]);
float zInter = zs[v0] + tEdge * (zs[v1] - zs[v0]);
float uInter = useTexture ? tu[v0] + tEdge * (tu[v1] - tu[v0]) : 0;
float vInter = useTexture ? tv[v0] + tEdge * (tv[v1] - tv[v0]) : 0;
if (xInter < xLeft) { xLeft = xInter; zLeft = zInter; uLeft = uInter; vLeft = vInter; }
if (xInter > xRight) { xRight = xInter; zRight = zInter; uRight = uInter; vRight = vInter; }
}
}
int xL = std::max(0, (int)xLeft);
int xR = std::min(zbufW - 1, (int)xRight);
for (int x = xL; x <= xR; x++) {
float tX = (xR == xL) ? 0.5f : float(x - xL) / (xR - xL);
float zVal = zLeft + tX * (zRight - zLeft);
if (useTexture) {
float u = uLeft + tX * (uRight - uLeft);
float v = vLeft + tX * (vRight - vLeft);
glTexCoord2f(u, v);
}
putPixel(x, y, zVal, cr, cg, cb);
}
}
}
}
}
if (useTexture) glDisable(GL_TEXTURE_2D);
}
// ---------- Callback: отрисовка ----------
void display() {
glClear(GL_COLOR_BUFFER_BIT);
initZBuffer(winW, winH);
drawSurface();
glutSwapBuffers();
}
// ---------- Callback: изменение размера окна ----------
void reshape(int w, int h) {
winW = w; winH = h;
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0, w, h, 0); // 2D-проекция для растеризации
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
initZBuffer(w, h);
}
// ---------- Меню ----------
enum { MENU_AXON, MENU_PERSP, MENU_WIRE, MENU_FILL, MENU_TEX, MENU_EXIT };
void menu(int item) {
switch (item) {
case MENU_AXON: perspective = false; break;
case MENU_PERSP: perspective = true; break;
case MENU_WIRE: wireframe = true; break;
case MENU_FILL: wireframe = false; break;
case MENU_TEX: useTexture = !useTexture; break;
case MENU_EXIT: exit(0);
}
glutPostRedisplay();
}
// ---------- Обработка клавиатуры ----------
void keyboard(unsigned char key, int, int) {
switch (key) {
// Вращение объекта
case 'a': alpha += 5.0 * PI / 180.0; break;
case 'd': alpha -= 5.0 * PI / 180.0; break;
case 'w': beta += 5.0 * PI / 180.0; break;
case 's': beta -= 5.0 * PI / 180.0; break;
// Переключение режимов
case 'p': perspective = !perspective; break;
case 'f': wireframe = !wireframe; break;
case 't': useTexture = !useTexture; break;
// Управление источником света
case '1': lightPos[0] += 0.5f; break; // X+
case '2': lightPos[0] -= 0.5f; break; // X-
case '3': lightPos[1] += 0.5f; break; // Y+
case '4': lightPos[1] -= 0.5f; break; // Y-
case '5': lightPos[2] += 0.5f; break; // Z+
case '6': lightPos[2] -= 0.5f; break; // Z-
// Свойства материала
case '7': matShininess = std::min(128.0f, matShininess + 8.0f); break;
case '8': matShininess = std::max(1.0f, matShininess - 8.0f); break;
case '9': // ярче диффузный свет
diffuseLight[0] = diffuseLight[1] = diffuseLight[2] =
std::min(1.0f, diffuseLight[0] + 0.1f); break;
case '0': // тусклее диффузный свет
diffuseLight[0] = diffuseLight[1] = diffuseLight[2] =
std::max(0.1f, diffuseLight[0] - 0.1f); break;
// Параметры поверхности
case '+': R += 0.1; break;
case '-': R = std::max(0.5, R - 0.1); break;
case 27: exit(0); // ESC
}
glutPostRedisplay();
}
// ---------- Инициализация ----------
void init() {
glClearColor(0.1f, 0.1f, 0.15f, 1.0f);
// Генерация текстуры
generateTexture();
// Создание меню
glutCreateMenu(menu);
glutAddMenuEntry("Аксонометрия", MENU_AXON);
glutAddMenuEntry("Перспектива", MENU_PERSP);
glutAddMenuEntry("Каркас (wireframe)", MENU_WIRE);
glutAddMenuEntry("Заливка (filled)", MENU_FILL);
glutAddMenuEntry("Текстура: вкл/выкл", MENU_TEX);
glutAddMenuEntry("Выход", MENU_EXIT);
glutAttachMenu(GLUT_RIGHT_BUTTON);
}
// ---------- Точка входа ----------
int main(int argc, char** argv) {
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
glutInitWindowSize(winW, winH);
glutCreateWindow("Сдвиг ~ B^2 | Вариант пользователя");
init();
glutDisplayFunc(display);
glutReshapeFunc(reshape);
glutKeyboardFunc(keyboard);
glutMainLoop();
return 0;
}