Serguntii
Помогающий
Offline
|
|
« : 10-11-2013 15:27 » |
|
Подскажите ,любые советы может где что прочитать, или может готовые примеры есть. Задачка такая нужно в vs C# сделать форму с темным окном 3D, внизу сетку. Из библиотеки функция постоянно возвращает координаты xyz нужно нарисовать точку и оставлять за ней след как она двигается. Еще бы конечно хотелось что бы можно было подвигать мышем для того что бы посмотреть с разных сторон. окна хватило бы 800*600. Насколько это реально, сложно?
|
|
« Последнее редактирование: 10-11-2013 15:31 от sergeyan »
|
Записан
|
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #1 : 10-11-2013 17:52 » |
|
Дак дело типовое даже вручную сделать.
1) Выбираешь мировую систему координат (в основном это максимумы и минимумы значений координат).
2) Выбираешь способ проецирования 3D на 2D и заводишь себе направления зрения как вектор.
3) Записываешь довольно элементарную функцию, которая отображает 3D точку в мировой системе координат на 2D точку окна.
4) Описываешь в мировой системе координат сетку.
5) Заводишь список исторических значений главной (движущейся) точки. И по мере добавления в список нового значения прорисовываешь весь хвост.
6) Обрабатываешь таскания мыши по вертикали и горизонтали как повороты вектора направления зрения вокруг, соответственно, вертикальной и горизонтальной осей. Обычно ещё добавляют колёсико для изменения масштаба (приблизить/отдалить).
7) По любому событию (появление новой точки или действия пользователя) перерисовываешь картинку.
|
|
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
Serguntii
Помогающий
Offline
|
|
« Ответ #2 : 10-11-2013 17:59 » |
|
а экран объект это opengl?
|
|
|
Записан
|
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #3 : 10-11-2013 18:20 » |
|
sergeyan, зачем? обычное окно.
|
|
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #5 : 10-11-2013 21:50 » |
|
Давно напрашивалось положить в анналы форума пример декомпозиции такой графической задачи, чтобы потом всех в неё тыкать. using System; using System.Drawing; using System.Collections.Generic; using System.Windows.Forms;
namespace Graph3D { // Основная программа: class Program { static void Main(string[] args) { // Делает лишь то, что запускает цикл приёма-обработки сообщений Windows // для единственного окна, которое привязано к модели. Application.Run(new Window()); } }
// Окно class Window : Form { // Модель. private Model model; // 2D представление. private View view; // Контроллер ввода пользователя. private Controller controller;
// Средства рисования Brush background; // кисть для фона Pen foreground; // перо для линий
// Масштабирующий коэффициент, который любой размер окна приводит к нормальному диапазону 0..1. // Позволяет абстрагировать изображение от размеров окна private float NormaScale { get { return 1.0f / Math.Min(this.ClientSize.Width, this.ClientSize.Height); } }
// Инициализация окна. public Window() { // Заголовок this.Text = "3D graphics"; // Создаём части и связываем их между собой this.model = new Model(); this.view = new View(this.model); this.controller = new Controller(this.view); // Включаем двойную буферизацию рисования, чтобы изображение не "мерцало". this.DoubleBuffered = true; // Создаём средства рисования. this.background = new SolidBrush(Color.Black); // чёрная сплошная кисть this.foreground = new Pen(Color.White); // белое перо нормальной толщины // Обрабатываем событие изменения 2D представления. this.view.Updated += new EventHandler(this.View_Updated); }
// Перехват нужных событий от Windows
// Отрисовка protected override void OnPaint(PaintEventArgs args) { base.OnPaint(args); // Очищаем фон. args.Graphics.FillRectangle(this.background, new Rectangle(new Point(0, 0), this.ClientSize)); // Вспомогательные коэффициенты float scale = this.NormaScale; // Масштаб. Избегаем двойного расчёта. // Сдвиги для неквадратного окна, чтобы изображение оказалось в центре. float shift = (this.ClientSize.Width - this.ClientSize.Height) / 2.0f; float shiftX = shift > 0.0f ? shift : 0.0f; float shiftY = shift < 0.0f ? -shift : 0.0f; // Рисуем все линии, которые генерирует 2D представление foreach(List<PointF> line in this.view.Lines) { // Каждую полученную точку приводим из нормальных в координаты окна: // центр из середины перемещаем в левый верхний угол, разворачиваем ось Y, // чтобы она была направлена вниз, центрируем изображение. PointF previousPoint = line[0]; previousPoint.X = (previousPoint.X + 0.5f) / scale + shiftX; previousPoint.Y = (0.5f - previousPoint.Y) / scale + shiftY; for(int i = 1; i < line.Count; ++i) { PointF nextPoint = line[i]; nextPoint.X = (nextPoint.X + 0.5f) / scale + shiftX; nextPoint.Y = (0.5f - nextPoint.Y) / scale + shiftY; args.Graphics.DrawLine(this.foreground, previousPoint, nextPoint); previousPoint = nextPoint; } } }
// Движения мыши protected override void OnMouseMove(MouseEventArgs args) { base.OnMouseMove(args); float scale = this.NormaScale; // Избегаем двойного расчёта. // Передаём информацию в контроллер. this.controller.Input( // Нажата ли кнопка мыши args.Button == MouseButtons.Left, // Приведённые координаты, сдвинутые таким образом, // чтобы центр был в середине, а ось Y ориентирована вверх new PointF(args.X * scale - 0.5f, 0.5f - args.Y * scale) ); }
// Изменение размеров окна protected override void OnResize(EventArgs args) { // При изменении размеров окна требуем от Windows перерисовки. this.Invalidate(); }
// Обработка внутренних событий
// Изменения в 2D представлении. private void View_Updated(object sender, EventArgs args) { // При изменении 2D представления требуем от Windows перерисовки. this.Invalidate(); } }
// Контроллер ввода пользователя class Controller { // Предыдущие координаты private PointF? previousCoordinates; // 2D представление, на которое влияет ввод. private View view;
// Инициализация public Controller(View view) { this.previousCoordinates = null; this.view = view; }
// Обработка входных данных от пользователя. public void Input(bool pressed, PointF coordinates) { // Пока есть нажатие, любое изменение координат рассматривается как угол полукруга, // на который нужно изменить углы поворота в ту или иную сторону. // Если нет нажатия, углы не меняются. if(!pressed) { this.previousCoordinates = null; } else { if(this.previousCoordinates.HasValue) { this.view.Rotate( Convert.ToSingle((coordinates.X - this.previousCoordinates.Value.X) * Math.PI), Convert.ToSingle((coordinates.Y - this.previousCoordinates.Value.Y) * Math.PI) ); } this.previousCoordinates = coordinates; } } }
// 2D представление class View { // Линии для вывода private List<List<PointF>> lines; // Углы ракурса обзора. private float horizontalAngle; private float verticalAngle; // 3D модель private Model model;
// Инициализация public View(Model model) { this.model = model; this.model.Updated += new EventHandler(this.Model_Updated); this.lines = new List<List<PointF>>(); this.horizontalAngle = 0.0f; this.verticalAngle = 0.0f; this.Update(); }
// Линии для вывода public List<List<PointF>> Lines { get { return this.lines; } }
// Вращение ракурса обзора. public void Rotate(float horizontalRotate, float verticalRotate) { // Меняем углы и устраняем многократное закручивание. float pi2 = Convert.ToSingle(Math.PI * 2.0); this.horizontalAngle += horizontalRotate; if(this.horizontalAngle > pi2) { this.horizontalAngle -= pi2; } if(this.horizontalAngle < pi2) { this.horizontalAngle += pi2; } this.verticalAngle += verticalRotate; if(this.verticalAngle > pi2) { this.verticalAngle -= pi2; } if(this.verticalAngle < pi2) { this.verticalAngle += pi2; } this.Update(); }
// Событие обновления public event EventHandler Updated; protected void OnUpdated() { if(this.Updated != null) { this.Updated(this, new EventArgs()); } }
// Отображение 3D модели в 2D представление. private void Update() { this.lines.Clear(); // Поворачиваем исходное 3D пространство на углы, // под которыми мы будем его рассматривать. // Отступаем по Z назад и смотрим на результат как на плоскость XY в перспективе. // Преобразованиям подвергается каждая точка. // Вспомогательные коэффициенты float sinV = Convert.ToSingle(Math.Sin(this.verticalAngle)); float sinH = Convert.ToSingle(Math.Sin(this.horizontalAngle)); float cosV = Convert.ToSingle(Math.Cos(this.verticalAngle)); float cosH = Convert.ToSingle(Math.Cos(this.horizontalAngle)); foreach(List<float[]> line3D in this.model.Lines) { List<PointF> line2D = new List<PointF>(); foreach(float[] point3D in line3D) { // Исходная точка float x0 = point3D[0]; float y0 = point3D[1]; float z0 = point3D[2]; // Вертикальный поворот вокруг оси X исходной точки - промежуточная точка 1 float x1 = x0; float y1 = y0 * cosV - z0 * sinV; float z1 = y0 * sinV + z0 * cosV; // Горизонтальный поворот вокруг оси Y промежуточной точки 1 - промежуточная точка 2 float x2 = x1 * cosH + z1 * sinH; float y2 = y1; float z2 = z1 * cosH - x1 * sinH; // Отступ по Z назад и добавление перспективы float p = z2 + 2; // отступаем с запасом, чтобы p не обратилось в 0. // Итоговая точка PointF point2D = new PointF(x2 / p, y2 / p); // по-хорошему нужно придумать обработку для p = 0 line2D.Add(point2D); } this.lines.Add(line2D); } this.OnUpdated(); }
// Обработка события изменение модели private void Model_Updated(object sender, EventArgs args) { this.Update(); this.OnUpdated(); } }
// 3D модель class Model { // Линии координатной сетки private List<List<float[]>> grid; // Линия графика private List<float[]> graph; // Таймер, по событию которого в график добавляется новая точка. private Timer timer;
// Инициализация public Model() { // Создаём линии сетки this.grid = new List<List<float[]>>(); for(float t = -0.5f; t <= 0.5f; t += 0.1f) { List<float[]> horizontal = new List<float[]>(); horizontal.Add(new float[] { -0.5f, t, 0.0f }); horizontal.Add(new float[] { 0.5f, t, 0.0f }); this.grid.Add(horizontal); List<float[]> vertical = new List<float[]>(); vertical.Add(new float[] { t, -0.5f, 0.0f }); vertical.Add(new float[] { t, 0.5f, 0.0f }); this.grid.Add(vertical); } this.graph = new List<float[]>(); // Создаём начальную точку графика this.graph.Add(new float[] { 0.0f, 0.0f, 0.0f }); // Настраиваем таймер this.timer = new Timer(); this.timer.Interval = 300; // обновление 1 раз в 300 мс this.timer.Tick += new EventHandler(this.Timer_Tick); this.timer.Start(); }
// Все линии в пространстве public List<List<float[]>> Lines { get { List<List<float[]>> lines = new List<List<float[]>>(this.grid); lines.Add(this.graph); return lines; } }
// Событие обновления public event EventHandler Updated; protected void OnUpdated() { if(this.Updated != null) { this.Updated(this, new EventArgs()); } }
// Обработка события таймера private void Timer_Tick(object sender, EventArgs args) { // Чтобы не накапливались события, если обработка их замедленна, // отключаем таймер на время обработки и включаем после неё. this.timer.Stop(); // Добавляем в график следующую точку. // График растёт по восходящей расширяющейся спирали. int steps = this.graph.Count; // номер следующего шага графика. float spiralAngle = Convert.ToSingle(Math.PI * 2.0 / 360.0 * steps); float spiralRadius = Convert.ToSingle(0.005 * steps); float x = Convert.ToSingle(spiralRadius * Math.Cos(spiralAngle)); float y = Convert.ToSingle(spiralRadius * Math.Sin(spiralAngle)); float z = spiralRadius / 3f; this.graph.Add(new float[] { x, y, z }); this.OnUpdated(); this.timer.Start(); } } } Всякие украшательства, как-то разные цвета линий, подписи, развороты в красивых ракурсах, масштабирование - это уже своими силами. Тут лишь основная идея показана: как уровень за уровнем разложить решение этой задачи на достаточно простые части с ограниченной ответственностью. За основу взята архитектура MVC, хотя Windows Forms под неё и не приспособлены.
|
|
« Последнее редактирование: 10-11-2013 21:59 от Dimka »
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
Serguntii
Помогающий
Offline
|
|
« Ответ #6 : 11-11-2013 09:03 » |
|
Сейчас попробую, если получиться сделать так будет просто великолепно.) Добавлено через 8 часов, 46 минут и 47 секунд:То что нужно, думал будет тормозить но работает даже на нетбуке. спасибо вам большое. Многим этот пример будет полезен. Добавлено через 19 дней, 22 часа, 44 минуты и 18 секунд:Как можно сотворить такое, сейчас экран от -0,5 до 0,5 приделы xyz меняются, после того как будут вычислены xyz пределы, как подогнать размер что бы картинка которая будет прорисовываться занимала 80% экрана окна? Спасибо за помощь.
|
|
« Последнее редактирование: 01-12-2013 16:34 от sergeyan »
|
Записан
|
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #7 : 01-12-2013 16:37 » |
|
sergeyan, Form - это наследник Control, если ты обращал внимание на иерархию. Это можно делать в любом Control, но не в любом Control есть, допустим, двойная буферизация вывода. Так что самое разумное - это сделать собственный UserControl, который можно класть куда угодно: хоть в окна, хоть во вкладки, хоть в панели.
|
|
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
Serguntii
Помогающий
Offline
|
|
« Ответ #8 : 01-12-2013 16:49 » |
|
Сообразил уже ) сделал наследование от usercontrol, теперь другой вопрос нужно как-то менять размеры что бы картинка помещалась в окно.
|
|
|
Записан
|
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #9 : 01-12-2013 17:15 » |
|
sergeyan, а кто мешает? В моём примере картинка масштабируется под размер окна.
|
|
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
Serguntii
Помогающий
Offline
|
|
« Ответ #10 : 01-12-2013 17:22 » |
|
Думаю правильнее? будет не переделывать код который вы написали , а подогнать точки что бы были в пределе от -0,5 до 0,5. Опишу алгоритм примерно: 1000 раз происходит событие в нем приходят аргументы xyz, это точки они могут быть как отрицательные так и положительные , В этом же событии нужно вычислить переменную scal . Если есть простой способ вычислить scal от xyz подскажите . Конечно придумаю просто может это уже есть в net что бы не придумывать велосипед?
Добавлено через 1 час, 18 минут и 25 секунд: сделал через список.
|
|
« Последнее редактирование: 01-12-2013 18:41 от sergeyan »
|
Записан
|
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #11 : 01-12-2013 18:55 » |
|
sergeyan, ну да. По списку найти min и max, затем разницу max-min нормализовать (вогнать в интервал 0...1) - т.е. подобрать коэффициент масштабирования. И домножать на него абсолютно все точки - всё пространство смасштабируется.
|
|
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
Serguntii
Помогающий
Offline
|
|
« Ответ #12 : 01-12-2013 20:59 » |
|
Что то все испортил я, хотел сделать что бы можно было цвет точки задавать получилась ошибка в строчке grid[0].Add(horizontal[0]); ( public class WindowGraph : UserControl { // Модель. private Model model; // 2D представление. private View view; // Контроллер ввода пользователя. private Controller controller;
// Средства рисования Brush background; // кисть для фона Pen foreground; // перо для линий
// Масштабирующий коэффициент, который любой размер окна приводит к нормальному диапазону 0..1. // Позволяет абстрагировать изображение от размеров окна private float NormaScale { get { return 1f / Math.Min(this.ClientSize.Width, this.ClientSize.Height); } }
// Инициализация окна. public WindowGraph() { // Заголовок this.Text = "3D graphics"; // Создаём части и связываем их между собой this.model = new Model(); this.view = new View(this.model); this.controller = new Controller(this.view); // Включаем двойную буферизацию рисования, чтобы изображение не "мерцало". this.DoubleBuffered = true; // Создаём средства рисования. this.background = new SolidBrush(Color.Black); // чёрная сплошная кисть this.foreground = new Pen(Color.White); // белое перо нормальной толщины // Обрабатываем событие изменения 2D представления. this.view.Updated += new EventHandler(this.View_Updated); }
// Перехват нужных событий от Windows
// Отрисовка protected override void OnPaint(PaintEventArgs args) { base.OnPaint(args); // Очищаем фон. args.Graphics.FillRectangle(this.background, new Rectangle(new Point(0, 0), this.ClientSize)); // Вспомогательные коэффициенты float scale = this.NormaScale; // Масштаб. Избегаем двойного расчёта. // Сдвиги для неквадратного окна, чтобы изображение оказалось в центре. float shift = (this.ClientSize.Width - this.ClientSize.Height) / 2.0f; float shiftX = shift > 0.0f ? shift : 0.0f; float shiftY = shift < 0.0f ? -shift : 0.0f; // Рисуем все линии, которые генерирует 2D представление foreach (List<PointF> line in this.view.Lines) { // Каждую полученную точку приводим из нормальных в координаты окна: // центр из середины перемещаем в левый верхний угол, разворачиваем ось Y, // чтобы она была направлена вниз, центрируем изображение. PointF previousPoint = line[0]; previousPoint.X = (previousPoint.X + 0.5f) / scale + shiftX; previousPoint.Y = (0.5f - previousPoint.Y) / scale + shiftY; for (int i = 1; i < line.Count; ++i) { PointF nextPoint = line[i]; nextPoint.X = (nextPoint.X + 0.5f) / scale + shiftX; nextPoint.Y = (0.5f - nextPoint.Y) / scale + shiftY; args.Graphics.DrawLine(this.foreground, previousPoint, nextPoint); previousPoint = nextPoint; } } }
// Движения мыши protected override void OnMouseMove(MouseEventArgs args) { base.OnMouseMove(args); float scale = this.NormaScale; // Избегаем двойного расчёта. // Передаём информацию в контроллер. this.controller.Input( // Нажата ли кнопка мыши args.Button == MouseButtons.Left, // Приведённые координаты, сдвинутые таким образом, // чтобы центр был в середине, а ось Y ориентирована вверх new PointF((float)(args.X * scale - 0.5f), (float)(0.5f - args.Y * scale)) ); }
// Изменение размеров окна protected override void OnResize(EventArgs args) { // При изменении размеров окна требуем от Windows перерисовки. this.Invalidate(); }
// Обработка внутренних событий
// Изменения в 2D представлении. private void View_Updated(object sender, EventArgs args) { // При изменении 2D представления требуем от Windows перерисовки. this.Invalidate(); }
public void addpoint3d_(double x, double y, double z,Color prn) { model.addpoint3d(x, y, z,prn); }
}
// Контроллер ввода пользователя class Controller { // Предыдущие координаты private PointF? previousCoordinates; // 2D представление, на которое влияет ввод. private View view;
// Инициализация public Controller(View view) { this.previousCoordinates = null; this.view = view; }
// Обработка входных данных от пользователя. public void Input(bool pressed, PointF coordinates) { // Пока есть нажатие, любое изменение координат рассматривается как угол полукруга, // на который нужно изменить углы поворота в ту или иную сторону. // Если нет нажатия, углы не меняются. if (!pressed) { this.previousCoordinates = null; } else { if (this.previousCoordinates.HasValue) { this.view.Rotate( Convert.ToSingle((coordinates.X - this.previousCoordinates.Value.X) * Math.PI), Convert.ToSingle((coordinates.Y - this.previousCoordinates.Value.Y) * Math.PI) ); } this.previousCoordinates = coordinates; } } }
// 2D представление class View { // Линии для вывода private List<List<PointF>> lines; // Углы ракурса обзора. private float horizontalAngle; private float verticalAngle; // 3D модель private Model model;
// Инициализация public View(Model model) { this.model = model; this.model.Updated += new EventHandler(this.Model_Updated); this.lines = new List<List<PointF>>(); this.horizontalAngle = 0.0f; this.verticalAngle = 0.0f; this.Update(); }
// Линии для вывода public List<List<PointF>> Lines { get { return this.lines; } }
// Вращение ракурса обзора. public void Rotate(float horizontalRotate, float verticalRotate) { // Меняем углы и устраняем многократное закручивание. float pi2 = Convert.ToSingle(Math.PI * 2.0); this.horizontalAngle += horizontalRotate; if (this.horizontalAngle > pi2) { this.horizontalAngle -= pi2; } if (this.horizontalAngle < pi2) { this.horizontalAngle += pi2; } this.verticalAngle += verticalRotate; if (this.verticalAngle > pi2) { this.verticalAngle -= pi2; } if (this.verticalAngle < pi2) { this.verticalAngle += pi2; } this.Update(); }
// Событие обновления public event EventHandler Updated; protected void OnUpdated() { if (this.Updated != null) { this.Updated(this, new EventArgs()); } }
// Отображение 3D модели в 2D представление. private void Update() { this.lines.Clear(); // Поворачиваем исходное 3D пространство на углы, // под которыми мы будем его рассматривать. // Отступаем по Z назад и смотрим на результат как на плоскость XY в перспективе. // Преобразованиям подвергается каждая точка. // Вспомогательные коэффициенты float sinV = Convert.ToSingle(Math.Sin(this.verticalAngle)); float sinH = Convert.ToSingle(Math.Sin(this.horizontalAngle)); float cosV = Convert.ToSingle(Math.Cos(this.verticalAngle)); float cosH = Convert.ToSingle(Math.Cos(this.horizontalAngle)); foreach (List<GraphLine> line3D in this.model.Lines) { List<PointF> line2D = new List<PointF>(); foreach (GraphLine point3D in line3D) { // Исходная точка float x0 = point3D.graphcl[0]; float y0 = point3D.graphcl[1]; float z0 = point3D.graphcl[2]; // Вертикальный поворот вокруг оси X исходной точки - промежуточная точка 1 float x1 = x0; float y1 = y0 * cosV - z0 * sinV; float z1 = y0 * sinV + z0 * cosV; // Горизонтальный поворот вокруг оси Y промежуточной точки 1 - промежуточная точка 2 float x2 = x1 * cosH + z1 * sinH; float y2 = y1; float z2 = z1 * cosH - x1 * sinH; // Отступ по Z назад и добавление перспективы float p = z2 + (float)1.2; // отступаем с запасом, чтобы p не обратилось в 0. // Итоговая точка PointF point2D = new PointF((float)(x2 / p),(float) (y2 / p)); // по-хорошему нужно придумать обработку для p = 0 line2D.Add(point2D); } this.lines.Add(line2D); } this.OnUpdated(); }
// Обработка события изменение модели private void Model_Updated(object sender, EventArgs args) { this.Update(); this.OnUpdated(); } }
// 3D модель class Model { public const float girdxmin = -0.5f; public const float girdxmax = 0.5f; // Линии координатной сетки private List<List<GraphLine>> grid; // Линия графика private List<GraphLine> graph;
// Инициализация public Model() { // Создаём линии сетки this.grid = new List<List<GraphLine>>(); for (float t = girdxmin; t <= girdxmax; t += 0.1f) { GraphLine[] horizontal = new GraphLine[2]; horizontal[0] = new GraphLine(girdxmax,t,0.0f,Color.White); horizontal[1] = new GraphLine(girdxmin,t,0.0f,Color.White); grid[0].Add(horizontal[0]); grid[1].Add(horizontal[1]);
GraphLine[] vertical = new GraphLine[2]; vertical[0] = new GraphLine(girdxmax, t, 0.0f, Color.White); vertical[1] = new GraphLine(girdxmin, t, 0.0f, Color.White); grid[0].Add(vertical[0]); grid[1].Add(vertical[1]);
} GraphLine graph0 = new GraphLine((float)0, (float)0, (float)0, Color.White); // Создаём начальную точку графика this.graph.Add(graph0);
}
// Все линии в пространстве public List<List<GraphLine>> Lines { get { List<List<GraphLine>> lines = new List<List<GraphLine>>(this.grid); lines.Add(this.graph); return lines; } }
// Событие обновления public event EventHandler Updated; protected void OnUpdated() { if (this.Updated != null) { this.Updated(this, new EventArgs()); } } public void addpoint3d(double x, double y, double z,Color clr) {
// Добавляем в график следующую точку. GraphLine graph0 = new GraphLine((float)x, (float)y, (float)z,clr); this.graph.Add(graph0); this.OnUpdated(); } }
public class GraphLine {
public Color cl; public float[] graphcl;
public GraphLine(float x, float y, float z, Color clo) { graphcl = new float[3]; graphcl[0] = x; graphcl[1] = y; graphcl[2] = z; cl = clo; }
}
|
|
|
Записан
|
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #13 : 02-12-2013 00:09 » |
|
sergeyan, я не понимаю: ты что, не видишь, что ты пишешь? Какой тип у grid? Что такое grid[0]? К чему применим метод Add?
|
|
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
Serguntii
Помогающий
Offline
|
|
« Ответ #14 : 02-12-2013 09:38 » |
|
Все исправил, нужно было поспать Может конечно что то улучшить нужно? public class WindowGraph : UserControl { // Модель. private Model model; // 2D представление. private View view; // Контроллер ввода пользователя. private Controller controller;
// Средства рисования Brush background; // кисть для фона Pen foreground; // перо для линий
// Масштабирующий коэффициент, который любой размер окна приводит к нормальному диапазону 0..1. // Позволяет абстрагировать изображение от размеров окна private float NormaScale { get { return 1f / Math.Min(this.ClientSize.Width, this.ClientSize.Height); } }
// Инициализация окна. public WindowGraph() { // Заголовок this.Text = "3D graphics"; // Создаём части и связываем их между собой this.model = new Model(); this.view = new View(this.model); this.controller = new Controller(this.view); // Включаем двойную буферизацию рисования, чтобы изображение не "мерцало". this.DoubleBuffered = true; // Создаём средства рисования. this.background = new SolidBrush(Color.Black); // чёрная сплошная кисть this.foreground = new Pen(Color.White); // белое перо нормальной толщины // Обрабатываем событие изменения 2D представления. this.view.Updated += new EventHandler(this.View_Updated); }
// Перехват нужных событий от Windows
// Отрисовка protected override void OnPaint(PaintEventArgs args) { base.OnPaint(args); // Очищаем фон. args.Graphics.FillRectangle(this.background, new Rectangle(new Point(0, 0), this.ClientSize)); // Вспомогательные коэффициенты float scale = this.NormaScale; // Масштаб. Избегаем двойного расчёта. // Сдвиги для неквадратного окна, чтобы изображение оказалось в центре. float shift = (this.ClientSize.Width - this.ClientSize.Height) / 2.0f; float shiftX = shift > 0.0f ? shift : 0.0f; float shiftY = shift < 0.0f ? -shift : 0.0f; // Рисуем все линии, которые генерирует 2D представление foreach (List<Pointfcl> line in this.view.Lines) { // Каждую полученную точку приводим из нормальных в координаты окна: // центр из середины перемещаем в левый верхний угол, разворачиваем ось Y, // чтобы она была направлена вниз, центрируем изображение. PointF previousPoint = line[0].pin; previousPoint.X = (previousPoint.X + 0.5f) / scale + shiftX; previousPoint.Y = (0.5f - previousPoint.Y) / scale + shiftY; for (int i = 1; i < line.Count; ++i) { PointF nextPoint = line[i].pin; nextPoint.X = (nextPoint.X + 0.5f) / scale + shiftX; nextPoint.Y = (0.5f - nextPoint.Y) / scale + shiftY; this.foreground.Color = line[i].clr; args.Graphics.DrawLine(this.foreground, previousPoint, nextPoint); previousPoint = nextPoint; } } }
// Движения мыши protected override void OnMouseMove(MouseEventArgs args) { base.OnMouseMove(args); float scale = this.NormaScale; // Избегаем двойного расчёта. // Передаём информацию в контроллер. this.controller.Input( // Нажата ли кнопка мыши args.Button == MouseButtons.Left, // Приведённые координаты, сдвинутые таким образом, // чтобы центр был в середине, а ось Y ориентирована вверх new PointF((float)(args.X * scale - 0.5f), (float)(0.5f - args.Y * scale)) ); }
// Изменение размеров окна protected override void OnResize(EventArgs args) { // При изменении размеров окна требуем от Windows перерисовки. this.Invalidate(); }
// Обработка внутренних событий
// Изменения в 2D представлении. private void View_Updated(object sender, EventArgs args) { // При изменении 2D представления требуем от Windows перерисовки. this.Invalidate(); }
public void addpoint3d_(double x, double y, double z,Color prn) { model.addpoint3d(x, y, z,prn); }
}
// Контроллер ввода пользователя class Controller { // Предыдущие координаты private PointF? previousCoordinates; // 2D представление, на которое влияет ввод. private View view;
// Инициализация public Controller(View view) { this.previousCoordinates = null; this.view = view; }
// Обработка входных данных от пользователя. public void Input(bool pressed, PointF coordinates) { // Пока есть нажатие, любое изменение координат рассматривается как угол полукруга, // на который нужно изменить углы поворота в ту или иную сторону. // Если нет нажатия, углы не меняются. if (!pressed) { this.previousCoordinates = null; } else { if (this.previousCoordinates.HasValue) { this.view.Rotate( Convert.ToSingle((coordinates.X - this.previousCoordinates.Value.X) * Math.PI), Convert.ToSingle((coordinates.Y - this.previousCoordinates.Value.Y) * Math.PI) ); } this.previousCoordinates = coordinates; } } }
// 2D представление class View { // Линии для вывода private List<List<Pointfcl>> lines; // Углы ракурса обзора. private float horizontalAngle; private float verticalAngle; // 3D модель private Model model;
// Инициализация public View(Model model) { this.model = model; this.model.Updated += new EventHandler(this.Model_Updated); this.lines = new List<List<Pointfcl>>(); this.horizontalAngle = 0.0f; this.verticalAngle = 0.0f; this.Update(); }
// Линии для вывода public List<List<Pointfcl>> Lines { get { return this.lines; } }
// Вращение ракурса обзора. public void Rotate(float horizontalRotate, float verticalRotate) { // Меняем углы и устраняем многократное закручивание. float pi2 = Convert.ToSingle(Math.PI * 2.0); this.horizontalAngle += horizontalRotate; if (this.horizontalAngle > pi2) { this.horizontalAngle -= pi2; } if (this.horizontalAngle < pi2) { this.horizontalAngle += pi2; } this.verticalAngle += verticalRotate; if (this.verticalAngle > pi2) { this.verticalAngle -= pi2; } if (this.verticalAngle < pi2) { this.verticalAngle += pi2; } this.Update(); }
// Событие обновления public event EventHandler Updated; protected void OnUpdated() { if (this.Updated != null) { this.Updated(this, new EventArgs()); } }
// Отображение 3D модели в 2D представление. private void Update() { this.lines.Clear(); // Поворачиваем исходное 3D пространство на углы, // под которыми мы будем его рассматривать. // Отступаем по Z назад и смотрим на результат как на плоскость XY в перспективе. // Преобразованиям подвергается каждая точка. // Вспомогательные коэффициенты float sinV = Convert.ToSingle(Math.Sin(this.verticalAngle)); float sinH = Convert.ToSingle(Math.Sin(this.horizontalAngle)); float cosV = Convert.ToSingle(Math.Cos(this.verticalAngle)); float cosH = Convert.ToSingle(Math.Cos(this.horizontalAngle)); foreach (List<GraphLine> line3D in this.model.Lines) { List<Pointfcl> line2D = new List<Pointfcl>(); foreach (GraphLine point3D in line3D) { // Исходная точка float x0 = point3D.graphcl[0]; float y0 = point3D.graphcl[1]; float z0 = point3D.graphcl[2]; // Вертикальный поворот вокруг оси X исходной точки - промежуточная точка 1 float x1 = x0; float y1 = y0 * cosV - z0 * sinV; float z1 = y0 * sinV + z0 * cosV; // Горизонтальный поворот вокруг оси Y промежуточной точки 1 - промежуточная точка 2 float x2 = x1 * cosH + z1 * sinH; float y2 = y1; float z2 = z1 * cosH - x1 * sinH; // Отступ по Z назад и добавление перспективы float p = z2 + (float)1.2; // отступаем с запасом, чтобы p не обратилось в 0. // Итоговая точка PointF point2D = new PointF((float)(x2 / p),(float) (y2 / p)); // по-хорошему нужно придумать обработку для p = 0 Pointfcl ppl = new Pointfcl(); ppl.pin = point2D; ppl.clr = point3D.cl; line2D.Add(ppl); } this.lines.Add(line2D); } this.OnUpdated(); }
// Обработка события изменение модели private void Model_Updated(object sender, EventArgs args) { this.Update(); this.OnUpdated(); } }
// 3D модель class Model { public const float girdxmin = -0.5f; public const float girdxmax = 0.5f; // Линии координатной сетки private List<List<GraphLine>> grid; // Линия графика private List<GraphLine> graph;
// Инициализация public Model() { // Создаём линии сетки this.grid = new List<List<GraphLine>>(); for (float t = girdxmin; t <= girdxmax; t += 0.1f) { Color clr = new Color(); if ((t >= -0.1f) && (t < 0.0f)) { clr = Color.Blue; } else { clr = Color.White; }
GraphLine[] horizontal = new GraphLine[2]; horizontal[0] = new GraphLine(girdxmax, t, 0.0f, clr); horizontal[1] = new GraphLine(girdxmin, t, 0.0f, clr); grid.Add(horizontal.ToList());
GraphLine[] vertical = new GraphLine[2]; vertical[0] = new GraphLine(t, girdxmax, 0.0f, clr); vertical[1] = new GraphLine(t, girdxmin, 0.0f, clr); grid.Add(vertical.ToList());
} this.graph = new List<GraphLine>(); GraphLine graph0 = new GraphLine((float)0, (float)0, (float)0, Color.White); // Создаём начальную точку графика this.graph.Add(graph0);
}
// Все линии в пространстве public List<List<GraphLine>> Lines { get { List<List<GraphLine>> lines = new List<List<GraphLine>>(this.grid); lines.Add(this.graph); return lines; } }
// Событие обновления public event EventHandler Updated; protected void OnUpdated() { if (this.Updated != null) { this.Updated(this, new EventArgs()); } } public void addpoint3d(double x, double y, double z,Color clr) {
// Добавляем в график следующую точку. GraphLine graph0 = new GraphLine((float)x, (float)y, (float)z,clr); this.graph.Add(graph0); this.OnUpdated(); } }
public class GraphLine {
public Color cl; public float[] graphcl; public GraphLine() {
} public GraphLine(float x, float y, float z, Color clo) { graphcl = new float[3]; graphcl[0] = x; graphcl[1] = y; graphcl[2] = z; cl = clo; }
}
public class Pointfcl { public PointF pin; public Color clr; public Pointfcl() {
}
}
Добавлено через 2 часа, 54 минуты и 4 секунды:С меня пиво с получки. Можно еще вопрос, незнаю насколько реально это сделать нужно когда колесико мыша крутим увеличивать картинку которая под курсором. сейчас получается иногда такое картинка как бы в угол забивается. код который ниже работает отлично. Как сделаю то что хотел, исходники выложу может кому пригодится помогите пожалуйста.
|
|
« Последнее редактирование: 02-12-2013 12:33 от sergeyan »
|
Записан
|
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #15 : 02-12-2013 12:39 » |
|
Если у тебя событие происходит 1000 раз в секунду, вся схема выглядит неудачно. Refresh графика с такой частотой 1000 fps бессмысленна со всех точек зрения - ни человек не может разглядеть, ни монитор отобразить. Достаточно частоты старого кинофильма - 24 кадра в секунду, для круглого счёта 25 - 40 мс.
В итоге нужно аккуратно модифицировать связь между поступлением точек в модель и отображением. Для этого либо в модель добавить таймер, который генерирует событие не чаще 25 раз в секунду по условию: в каждый тик таймера смотрим, добавились ли новые точки за период таймера, и если да, генерируем событие, если нет - не генерируем. Либо сделать модель не активной, а пассивной (без события), а таймер поместить, например, в usercontrol. Тогда вместо invalidate во всех местах нужно устанавливать флаг refreshRequired, а в обработчике таймера, если флаг установлен, вызывать invalidate и сбрасывать флаг. Логика работы view и model должна быть изменена с "выталкивающей", на "вытягивающую". Т.е. в onpaint делать запрос к view, и только в этот момент view начинает проецирование точек 3D в 2D, запросив у модели текущее состояние. Т.е. расчёты выполняются строго по запросу в момент рисования, а не заранее в момент добавления новой точки.
|
|
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
Serguntii
Помогающий
Offline
|
|
« Ответ #16 : 02-12-2013 12:47 » |
|
Все отлично работает, осталась одна проблема нужно колесиком делать увеличении место под курсором.
|
|
|
Записан
|
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #17 : 02-12-2013 12:52 » |
|
sergeyan, размер картинки - это строчка, где добавляется точечная перспектива. // Отступ по Z назад и добавление перспективы float p = z2 + (float)1.2; // отступаем с запасом, чтобы p не обратилось в 0. (Писать "(float)1.2" не нужно, числа типа float имеют суффикс "f" - т.е. "1.2f" - это сразу число нужного типа как константа в коде. И даже в C++ так же.) Чтобы картинка отодвинулась, нужно увеличить отступ по Z - т.е. отойти подальше, тогда в силу точечной перспективы (чем дальше, тем размеры меньше и всё сжимается к точке - центру перспективы) изображение уменьшится. Чтобы приблизилась - уменьшить отступ по Z. Одновременно может быть полезным играться с коэффициентом усиления перспективы (т.е. домножать всё выражение на какое-то значение). Чем сильнее перспектива, тем быстрее сходимость дальних объектов к точке, и сами объекты визуально выглядят как "гигантские" (что удобно при разглядывании вблизи - при малом отступе), и наоборот, чем слабее перспектива, тем объекты визуально выглядят как "миниатюрные" (что удобно при разглядывании издали). Главный вопрос, что делать с частями картинки, выходящими за спину наблюдателя - когда p обращается в 0 или становится отрицательным. Чтобы при этом не возникло отрицательных x и y, и линии не соскакивали куда-то за экран, правильно такие точки выбрасывать и не рисовать. А вместе с такими точками и те отрезки, в которых они встречаются. Т.е. должна быть секущая плоскость. В моём коде этот момент не был реализован, чтобы не загромождать главное. Но по построению линий сетки сильное приближение и заход внутрь сетки приведут к тому, что вся сетка не отобразится. Сетку придётся переделать: заменить длинные отрезки во всю длину последовательностями коротких - на длину стороны клетки. Для графика это неактуально - он сразу рисуется небольшими отрезками (надеюсь).
|
|
« Последнее редактирование: 02-12-2013 12:56 от Dimka »
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
|
|