├── CubeSharp ├── .gitignore ├── CubeSharp.csproj ├── CubeSharp.sln ├── FuncPanel.cs ├── MainWindow.cs ├── MeshFactory.cs ├── MeshGraph.cs ├── Renderer.cs ├── packages.config └── tests │ └── MeshGraphTests.cs ├── README.md └── docs ├── scrshot-box-select.png ├── scrshot-connected.png ├── scrshot-cs-shaded.png ├── scrshot-cs-wireframe.png ├── scrshot-extrude.png ├── scrshot-joined.png ├── scrshot-not-joined.png ├── scrshot-rotated.png ├── scrshot-scaled.png ├── scrshot-select-all.png ├── scrshot-select-neibour.png ├── scrshot-splitted-cube.png ├── scrshot-translated.png └── scrshot-vertices.png /CubeSharp/.gitignore: -------------------------------------------------------------------------------- 1 | #Autosave files 2 | *~ 3 | 4 | #build 5 | [Oo]bj/ 6 | [Bb]in/ 7 | packages/ 8 | TestResults/ 9 | 10 | # globs 11 | Makefile.in 12 | *.DS_Store 13 | *.sln.cache 14 | *.suo 15 | *.cache 16 | *.pidb 17 | *.userprefs 18 | *.usertasks 19 | config.log 20 | config.make 21 | config.status 22 | aclocal.m4 23 | install-sh 24 | autom4te.cache/ 25 | *.user 26 | *.tar.gz 27 | tarballs/ 28 | test-results/ 29 | Thumbs.db 30 | 31 | #Mac bundle stuff 32 | *.dmg 33 | *.app 34 | 35 | #resharper 36 | *_Resharper.* 37 | *.Resharper 38 | 39 | #dotCover 40 | *.dotCover 41 | 42 | TestResult.xml 43 | -------------------------------------------------------------------------------- /CubeSharp/CubeSharp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Debug 4 | AnyCPU 5 | {534275F9-6947-4E38-B09F-1FDAA0DA114F} 6 | Exe 7 | CubeSharp 8 | CubeSharp 9 | v4.5 10 | publish\ 11 | true 12 | Disk 13 | false 14 | Foreground 15 | 7 16 | Days 17 | false 18 | false 19 | true 20 | 0 21 | 1.0.0.%2a 22 | false 23 | false 24 | true 25 | 26 | 27 | true 28 | full 29 | false 30 | bin\Debug 31 | DEBUG; 32 | prompt 33 | 4 34 | false 35 | 36 | 37 | 38 | 39 | 40 | true 41 | 42 | 43 | full 44 | true 45 | bin\Release 46 | prompt 47 | 4 48 | false 49 | true 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | ..\..\..\.nuget\OpenTK.GLControl.1.1.2349.61993\lib\NET40\OpenTK.GLControl.dll 58 | 59 | 60 | ..\..\..\.nuget\OpenTK.1.1.2349.61993\lib\NET40\OpenTK.dll 61 | 62 | 63 | ..\..\..\.nuget\NUnit.3.6.1\lib\net45\nunit.framework.dll 64 | 65 | 66 | 67 | 68 | Form 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | False 83 | Microsoft .NET Framework 4.5 %28x86 and x64%29 84 | true 85 | 86 | 87 | False 88 | .NET Framework 3.5 SP1 89 | false 90 | 91 | 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /CubeSharp/CubeSharp.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2012 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CubeSharp", "CubeSharp.csproj", "{534275F9-6947-4E38-B09F-1FDAA0DA114F}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|Any CPU = Debug|Any CPU 9 | Release|Any CPU = Release|Any CPU 10 | EndGlobalSection 11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 12 | {534275F9-6947-4E38-B09F-1FDAA0DA114F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 13 | {534275F9-6947-4E38-B09F-1FDAA0DA114F}.Debug|Any CPU.Build.0 = Debug|Any CPU 14 | {534275F9-6947-4E38-B09F-1FDAA0DA114F}.Release|Any CPU.ActiveCfg = Release|Any CPU 15 | {534275F9-6947-4E38-B09F-1FDAA0DA114F}.Release|Any CPU.Build.0 = Release|Any CPU 16 | EndGlobalSection 17 | EndGlobal 18 | -------------------------------------------------------------------------------- /CubeSharp/FuncPanel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | using System.Windows.Forms; 5 | using System.Drawing; 6 | using System.Collections.Generic; 7 | using OpenTK; 8 | using OpenTK.Graphics; 9 | 10 | namespace CubeSharp 11 | { 12 | public class FuncPanel : Panel { 13 | public MainWindow ParentWindow; 14 | 15 | TableLayoutPanel container; 16 | Dictionary groups; 17 | 18 | TableLayoutPanel CreateGroup(string title) { 19 | GroupBox gb = new GroupBox(); 20 | gb.Text = title; 21 | gb.Name = "gb_" + title; 22 | gb.AutoSize = true; 23 | gb.Dock = DockStyle.Top; 24 | gb.Padding = new Padding(10, 10, 10, 0); 25 | 26 | TableLayoutPanel gb_cont = new TableLayoutPanel(); 27 | gb_cont.AutoSize = true; 28 | gb_cont.Dock = DockStyle.Top; 29 | 30 | gb.Controls.Add(gb_cont); 31 | groups[title] = gb_cont; 32 | container.Controls.Add(gb, 0, container.RowCount); 33 | 34 | return gb_cont; 35 | } 36 | 37 | Button button_pressed; 38 | delegate void Func(); 39 | 40 | public FuncPanel() { 41 | this.AutoScroll = true; 42 | 43 | groups = new Dictionary(); 44 | container = new TableLayoutPanel(); 45 | container.Dock = DockStyle.Top; 46 | container.AutoSize = true; 47 | 48 | foreach(MethodInfo mi in this.GetType().GetMethods()) { 49 | string[] parts = mi.Name.Split('_'); 50 | TableLayoutPanel gb; 51 | 52 | if(parts.Length < 3) 53 | continue; 54 | if(parts[0] != "f") 55 | continue; 56 | if(!groups.TryGetValue(parts[1], out gb)) 57 | gb = CreateGroup(parts[1]); 58 | 59 | string title = ""; 60 | for(int i = 2; i < parts.Length; i++) 61 | title += parts[i] + (i == parts.Length - 1 ? "" : " "); 62 | 63 | Button btn = new Button(); 64 | btn.Text = title; 65 | btn.Dock = DockStyle.Top; 66 | btn.FlatStyle = FlatStyle.System; 67 | btn.Click += (o, e) => { 68 | button_pressed = btn; 69 | try { 70 | Func func = (Func) mi.CreateDelegate(typeof(Func), this); 71 | func(); 72 | } catch(Exception exc) { 73 | MessageBox.Show(exc.Message); 74 | Console.WriteLine(exc.StackTrace); 75 | } 76 | }; 77 | 78 | gb.Controls.Add(btn, 0, gb.RowCount); 79 | } 80 | 81 | Controls.Add(container); 82 | } 83 | 84 | MeshGraph model { 85 | get { return ParentWindow.Model; } 86 | } 87 | 88 | protected void show_menu() { 89 | if(button_pressed != null && button_pressed.ContextMenuStrip != null) 90 | button_pressed.ContextMenuStrip.Show( 91 | button_pressed.PointToScreen(new Point(0, 0)) + 92 | new Size(0, button_pressed.Size.Height)); 93 | } 94 | 95 | public void f_Edit_Translate() { 96 | ParentWindow.CurrentTransformer = TransformerType.TranslationTransformer; 97 | } 98 | 99 | public void f_Edit_Scale() { 100 | ParentWindow.CurrentTransformer = TransformerType.ScalingTransformer; 101 | } 102 | 103 | public void f_Edit_Rotate() { 104 | ParentWindow.CurrentTransformer = TransformerType.RotationTransformer; 105 | } 106 | 107 | public void f_Edit_Delete() { 108 | if(button_pressed.ContextMenuStrip == null) { 109 | ContextMenuStrip cms = new ContextMenuStrip(); 110 | var delete_vertices = cms.Items.Add("Delete Vertices"); 111 | var delete_edges = cms.Items.Add("Delete Edges"); 112 | var delete_facets = cms.Items.Add("Delete Facets"); 113 | 114 | delete_vertices.Click += (o, e) => { 115 | var s = new HashSet(model.SelectedVertices); 116 | foreach(MeshVertex v in s) 117 | model.RemoveVertex(v); 118 | model.UpdateAll(); 119 | }; 120 | 121 | delete_edges.Click += (o, e) => { 122 | var s = new HashSet(model.SelectedEdges); 123 | foreach(MeshEdge edge in s) 124 | model.RemoveEdge(edge); 125 | model.UpdateAll(); 126 | }; 127 | 128 | delete_facets.Click += (o, e) => { 129 | var s = new HashSet(model.SelectedFacets); 130 | foreach(MeshFacet f in s) 131 | model.RemoveFacet(f); 132 | model.UpdateAll(); 133 | }; 134 | 135 | button_pressed.ContextMenuStrip = cms; 136 | } 137 | 138 | show_menu(); 139 | } 140 | 141 | public void f_Edit_Connect() { 142 | if(model.SelectedVertices.Count == 2) { 143 | model.AddEdge(model.SelectedVertices.First(), 144 | model.SelectedVertices.Last(), true); 145 | } else if(model.SelectedVertices.Count == 3) { 146 | // There should always be one edge that has connected with 147 | // another facet to create a correct facet, otherwise the 148 | // algorithm will treat the direction of camera as positive. 149 | MeshVertex[] vs = new MeshVertex[3]; 150 | int vi = 0; 151 | foreach(MeshVertex v in model.SelectedVertices) 152 | vs[vi++] = v; 153 | 154 | Vector3 cam_dir = ParentWindow.MainCamera.Position - 155 | vs[0].Position; 156 | 157 | ParentWindow.Model.AddTriangle(cam_dir, vs); 158 | } else 159 | throw new Exception("Can only connect 2 vertices with an edge or 3 vertices with a triangle"); 160 | 161 | model.UpdateAll(); 162 | } 163 | 164 | public void f_Edit_Join() { 165 | // find outer edges and duplicated(inner) edges 166 | Dictionary edtable = new Dictionary(); 167 | HashSet dup_edges = new HashSet(); 168 | foreach(MeshFacet f in ParentWindow.Model.SelectedFacets) { 169 | foreach(MeshEdge e in f.Edges) { 170 | if(dup_edges.Contains(e)) { 171 | continue; 172 | } else if(edtable.ContainsKey(e)) { 173 | edtable.Remove(e); 174 | dup_edges.Add(e); 175 | continue; 176 | } 177 | 178 | edtable.Add(e, e.F1 == f ? true : false); 179 | } 180 | } 181 | 182 | List vs = new List(); 183 | vs.Add(edtable.First().Value ? edtable.First().Key.V1 : edtable.First().Key.V2); 184 | MeshVertex cur_v = edtable.First().Value ? edtable.First().Key.V2 : edtable.First().Key.V1; 185 | 186 | while(cur_v != vs[0]) { 187 | vs.Add(cur_v); 188 | foreach(MeshEdge e in cur_v.Edges) { 189 | if(edtable.ContainsKey(e)) { 190 | if(edtable[e] && e.V1 == cur_v) { 191 | cur_v = e.V2; 192 | break; 193 | } 194 | if(!edtable[e] && e.V2 == cur_v) { 195 | cur_v = e.V1; 196 | break; 197 | } 198 | } 199 | } 200 | 201 | if(cur_v == vs.Last()) 202 | throw new Exception("Unknown Error"); 203 | } 204 | 205 | List oldfacets = new List(ParentWindow.Model.SelectedFacets); 206 | foreach(MeshFacet f in oldfacets) ParentWindow.Model.RemoveFacet(f); 207 | foreach(MeshEdge e in dup_edges) ParentWindow.Model.RemoveEdge(e); 208 | 209 | ParentWindow.Model.AddFacet(vs.ToArray()); 210 | ParentWindow.Model.UpdateAll(); 211 | } 212 | 213 | public void f_Edit_Extrude() { 214 | // maintaining the oldvertex-newvertex table 215 | Dictionary ontable = new Dictionary(); 216 | foreach(MeshFacet f in ParentWindow.Model.SelectedFacets) { 217 | foreach(MeshVertex v in f.Vertices) { 218 | if(ontable.ContainsKey(v)) continue; 219 | ontable.Add(v, ParentWindow.Model.AddVertex(v.Position)); 220 | } 221 | } 222 | 223 | // copy facets and maintain (old)edge-direction table 224 | Dictionary edtable = new Dictionary(); 225 | HashSet dup_edges = new HashSet(); 226 | List newfacets = new List(); 227 | foreach(MeshFacet f in ParentWindow.Model.SelectedFacets) { 228 | newfacets.Add(ParentWindow.Model.AddFacet( 229 | f.Vertices.Select(x => ontable[x]).ToArray())); 230 | 231 | // delete (old)edge when duplicated 232 | foreach(MeshEdge e in f.Edges) { 233 | if(dup_edges.Contains(e)) { 234 | continue; 235 | } else if(edtable.ContainsKey(e)) { 236 | edtable.Remove(e); 237 | dup_edges.Add(e); 238 | continue; 239 | } 240 | 241 | edtable.Add(e, e.F1 == f ? true : false); 242 | } 243 | } 244 | 245 | // delete dup edges and old facets 246 | List oldfacets = new List(ParentWindow.Model.SelectedFacets); 247 | foreach(MeshFacet f in oldfacets) ParentWindow.Model.RemoveFacet(f); 248 | foreach(MeshEdge e in dup_edges) ParentWindow.Model.RemoveEdge(e); 249 | 250 | // construct cliff 251 | foreach(var ed in edtable) { 252 | if(ed.Value) { 253 | ParentWindow.Model.AddFacet(ed.Key.V1, ed.Key.V2, 254 | ontable[ed.Key.V2], ontable[ed.Key.V1]); 255 | } 256 | if(!ed.Value) { 257 | ParentWindow.Model.AddFacet(ed.Key.V2, ed.Key.V1, 258 | ontable[ed.Key.V1], ontable[ed.Key.V2]); 259 | } 260 | } 261 | 262 | ParentWindow.Model.DeselectAll(); 263 | 264 | foreach(MeshFacet f in newfacets) { 265 | f.Selected = true; 266 | foreach(MeshEdge e in f.Edges) { 267 | e.Selected = true; 268 | e.V1.Selected = true; 269 | e.V2.Selected = true; 270 | } 271 | } 272 | 273 | ParentWindow.Model.UpdateAll(); 274 | } 275 | 276 | public void f_Edit_Split() { 277 | ParentWindow.IsSplitting = true; 278 | } 279 | 280 | public void f_File_Import() { 281 | var dialog = new OpenFileDialog(); 282 | dialog.Filter = "Wavefront Object (*.obj)|*.obj"; 283 | if(dialog.ShowDialog(this) == DialogResult.OK) { 284 | WavefrontFactory importer = new WavefrontFactory(); 285 | importer.FileName = dialog.FileName; 286 | importer.AddMeshGraphUpon(ref ParentWindow.Model); 287 | ParentWindow.Model.UpdateAll(); 288 | } 289 | } 290 | 291 | public void f_File_Export() { 292 | var dialog = new SaveFileDialog(); 293 | dialog.Filter = "Wavefront Object (*.obj)|*.obj"; 294 | if(dialog.ShowDialog(this) == DialogResult.OK) { 295 | WavefrontFactory importer = new WavefrontFactory(); 296 | importer.FileName = dialog.FileName; 297 | importer.ExportMesh(ParentWindow.Model); 298 | } 299 | } 300 | 301 | public void f_Selection_Select_All() { 302 | foreach(MeshVertex v in ParentWindow.Model.Vertices) 303 | v.Selected = true; 304 | foreach(MeshEdge e in ParentWindow.Model.Edges) 305 | e.Selected = true; 306 | foreach(MeshFacet f in ParentWindow.Model.Facets) 307 | f.Selected = true; 308 | 309 | ParentWindow.Model.UpdateAll(); 310 | } 311 | 312 | public void f_Selection_Select_Neighbours() { 313 | Stack vstack = new Stack(ParentWindow.Model.SelectedVertices); 314 | ParentWindow.Model.DeselectAll(); 315 | 316 | while(vstack.Count > 0) { 317 | MeshVertex v = vstack.Pop(); 318 | if(v.Selected) continue; 319 | v.Selected = true; 320 | 321 | foreach(MeshEdge e in v.Edges) { 322 | e.Selected = true; 323 | if(e.F1 != null) e.F1.Selected = true; 324 | if(e.F2 != null) e.F2.Selected = true; 325 | } 326 | 327 | foreach(MeshVertex adj in v.AdjacencyVertices) { 328 | vstack.Push(adj); 329 | } 330 | } 331 | 332 | ParentWindow.Model.UpdateAll(); 333 | } 334 | 335 | public void f_Selection_Deselect_All() { 336 | ParentWindow.Model.DeselectAll(); 337 | ParentWindow.Model.UpdateAll(); 338 | } 339 | 340 | public void f_Selection_Box_Select() { 341 | if(button_pressed.ContextMenuStrip == null) { 342 | ContextMenuStrip cms = new ContextMenuStrip(); 343 | var select_vertices = cms.Items.Add("Select Vertices"); 344 | var select_edges = cms.Items.Add("Select Edges"); 345 | var select_facets = cms.Items.Add("Select Facets"); 346 | 347 | select_vertices.Click += (o, e) => { 348 | ParentWindow.BoxSelectingType = ObjectType.ModelVertex; 349 | }; 350 | 351 | select_edges.Click += (o, e) => { 352 | ParentWindow.BoxSelectingType = ObjectType.ModelEdge; 353 | }; 354 | 355 | select_facets.Click += (o, e) => { 356 | ParentWindow.BoxSelectingType = ObjectType.ModelFacet; 357 | }; 358 | 359 | button_pressed.ContextMenuStrip = cms; 360 | } 361 | 362 | show_menu(); 363 | } 364 | 365 | public void f_View_Wireframe() { 366 | ParentWindow.IsWireframe = true; 367 | } 368 | 369 | public void f_View_Shaded() { 370 | ParentWindow.Model.FacetDataWithNormal.UpdateData(); 371 | ParentWindow.IsWireframe = false; 372 | } 373 | 374 | public void f_View_Reset_Camera() { 375 | ParentWindow.MainCamera.Tf = new Transformation(); 376 | ParentWindow.MainCamera.Tf.RotateY(-Math.PI / 6); 377 | ParentWindow.MainCamera.Tf.RotateX(-Math.PI / 6); 378 | ParentWindow.MainCamera.Tf.Translate(0, 0, 5); 379 | } 380 | 381 | public void f_Create_Vertex() { 382 | ParentWindow.Model.DeselectAll(); 383 | ParentWindow.Model.AddVertex(0, 0, 0).Selected = true; 384 | } 385 | 386 | public void f_Create_Plane() { 387 | ParentWindow.Model.DeselectAll(); 388 | new PlaneFactory().AddMeshGraphUpon(ref ParentWindow.Model); 389 | ParentWindow.Model.UpdateAll(); 390 | } 391 | 392 | public void f_Create_Cube() { 393 | ParentWindow.Model.DeselectAll(); 394 | new BoxMeshFactory().AddMeshGraphUpon(ref ParentWindow.Model); 395 | ParentWindow.Model.UpdateAll(); 396 | } 397 | 398 | public void f_Create_Sphere() { 399 | ParentWindow.Model.DeselectAll(); 400 | new UVSphereFactory().AddMeshGraphUpon(ref ParentWindow.Model); 401 | ParentWindow.Model.UpdateAll(); 402 | } 403 | 404 | public void f_Create_Cylinder() { 405 | ParentWindow.Model.DeselectAll(); 406 | new CylinderFactory().AddMeshGraphUpon(ref ParentWindow.Model); 407 | ParentWindow.Model.UpdateAll(); 408 | } 409 | } 410 | } 411 | -------------------------------------------------------------------------------- /CubeSharp/MainWindow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Windows.Forms; 5 | using System.Drawing; 6 | using OpenTK; 7 | using OpenTK.Graphics; 8 | using OpenTK.Graphics.OpenGL; 9 | 10 | namespace CubeSharp 11 | { 12 | public enum TransformerType { 13 | TranslationTransformer, 14 | ScalingTransformer, 15 | RotationTransformer, 16 | } 17 | 18 | public enum ObjectType { 19 | None = 0, 20 | 21 | ModelFacet = 1, 22 | ModelEdge = 2, 23 | ModelVertex = 3, 24 | TranslationTransformer = 4, 25 | ScalingTransformer = 5, 26 | RotationTransformer = 6, 27 | } 28 | 29 | public enum DragState { 30 | None = 0, 31 | 32 | LeftButton, 33 | MiddleButton, 34 | RightButton, 35 | 36 | RawStates, 37 | 38 | CameraRotation, 39 | CameraTranslation, 40 | TranslationTransformer, 41 | ScalingTransformer, 42 | RotationTransformer, 43 | BoxSelect, 44 | } 45 | 46 | public class ObjectMapElement { 47 | public int Index = 0; 48 | public ObjectType Type = ObjectType.None; 49 | public Vector3 Position; 50 | public int ScreenX = -1; 51 | public int ScreenY = -1; 52 | } 53 | 54 | public class MainWindow : Form 55 | { 56 | // Attrib 57 | public MeshGraph Model; 58 | // Uniform 59 | public Camera MainCamera; 60 | // Output 61 | RenderTarget obj_map; 62 | float[,,] obj_map_buffer; 63 | 64 | // Shaders 65 | WireframeRenderer wfrdr; 66 | ShadedRenderer srdr; 67 | ObjectMapRenderer crdr; 68 | GridRenderer grdr; 69 | TranslationTransformerRenderer tcrdr; 70 | ScalingTransformerRenderer scrdr; 71 | RotationTransformerRenderer rcrdr; 72 | SelectionBoxRenderer sbrdr; 73 | 74 | // Controls 75 | GLControl glc; 76 | FuncPanel func_panel; 77 | Timer timer; 78 | 79 | public MainWindow() 80 | { 81 | Fake_InitializeComponent(); 82 | 83 | DragInfo = new DragInfo_(); 84 | OpObject = new ObjectMapElement(); 85 | } 86 | 87 | private void Fake_InitializeComponent() 88 | { 89 | glc = new GLControl(GraphicsMode.Default, 3, 3, GraphicsContextFlags.Default); 90 | func_panel = new FuncPanel(); 91 | timer = new Timer(); 92 | 93 | timer.Tick += (o, e) => glc.Invalidate(); 94 | timer.Interval = 16; 95 | timer.Start(); 96 | 97 | glc.Name = "glc"; 98 | glc.VSync = true; 99 | glc.Paint += glc_Paint; 100 | glc.MouseMove += glc_MouseMove; 101 | glc.MouseMove += (o, e) => glc.Focus(); 102 | glc.MouseUp += glc_MouseUp; 103 | glc.MouseDown += glc_MouseDown; 104 | glc.MouseWheel += glc_MouseWheel; 105 | glc.KeyDown += ModifierKeyDown; 106 | glc.KeyUp += ModifierKeyUp; 107 | glc.TabIndex = 0; 108 | 109 | func_panel.MouseMove += (o, e) => func_panel.Focus(); 110 | func_panel.ParentWindow = this; 111 | func_panel.KeyDown += ModifierKeyDown; 112 | func_panel.KeyUp += ModifierKeyUp; 113 | 114 | Text = "CubeSharp"; 115 | Name = "CubeSharp"; 116 | Size = new Size(800, 600); 117 | Font = new Font("sans", 10); 118 | 119 | this.Load += CubeSharp_Load; 120 | this.Resize += CubeSharp_Resize; 121 | Controls.Add(glc); 122 | Controls.Add(func_panel); 123 | } 124 | 125 | private void CubeSharp_Load(object sender, EventArgs e) 126 | { 127 | Console.WriteLine(GL.GetString(StringName.Version)); 128 | 129 | wfrdr = new WireframeRenderer(); 130 | srdr = new ShadedRenderer(); 131 | crdr = new ObjectMapRenderer(); 132 | grdr = new GridRenderer(); 133 | tcrdr = new TranslationTransformerRenderer(); 134 | scrdr = new ScalingTransformerRenderer(); 135 | rcrdr = new RotationTransformerRenderer(); 136 | sbrdr = new SelectionBoxRenderer(); 137 | 138 | Model = new BoxMeshFactory().GenerateMeshGraph(); 139 | MainCamera = new Camera(); 140 | obj_map = new RenderTarget(PixelType.Float, Viewport); 141 | 142 | Model.EdgeData.UpdateData(); 143 | Model.VertexData.UpdateData(); 144 | Model.FacetData.UpdateData(); 145 | MainCamera.Tf.RotateY(-Math.PI / 6); 146 | MainCamera.Tf.RotateX(-Math.PI / 6); 147 | MainCamera.Tf.Translate(0, 0, 5); 148 | 149 | wfrdr.Camera = MainCamera; 150 | wfrdr.Model = Model; 151 | srdr.Camera = MainCamera; 152 | srdr.Model = Model; 153 | crdr.Camera = MainCamera; 154 | crdr.Model = Model; 155 | grdr.Camera = MainCamera; 156 | tcrdr.Camera = MainCamera; 157 | tcrdr.Model = Model; 158 | scrdr.Camera = MainCamera; 159 | scrdr.Model = Model; 160 | rcrdr.Camera = MainCamera; 161 | rcrdr.Model = Model; 162 | 163 | CubeSharp_Resize(null, null); 164 | } 165 | 166 | private void CubeSharp_Resize(object sender, EventArgs e) 167 | { 168 | glc.Location = new Point(0, 0); 169 | glc.Size = ClientSize - new Size(225, 0); 170 | func_panel.Location = glc.Location + new Size(glc.Size.Width, 0); 171 | func_panel.Size = new Size(225, glc.Size.Height); 172 | 173 | obj_map.Size = Viewport; 174 | obj_map_buffer = null; 175 | MainCamera.Ratio = Viewport.Width / (double) Viewport.Height; 176 | } 177 | 178 | private void glc_Paint(object sender, EventArgs e) 179 | { 180 | glc.MakeCurrent(); 181 | GL.Viewport(0, 0, Viewport.Width, Viewport.Height); 182 | 183 | //////////////////////////////////// 184 | RenderTarget.Screen.Use(); 185 | 186 | GL.Enable(EnableCap.Multisample); 187 | GL.ClearColor(0.2f, 0.2f, 0.2f, 0); 188 | GL.Clear(ClearBufferMask.ColorBufferBit); 189 | GL.Clear(ClearBufferMask.DepthBufferBit); 190 | 191 | grdr.Render(RenderTarget.Screen); 192 | 193 | if(IsWireframe) { 194 | wfrdr.Render(RenderTarget.Screen); 195 | } else { 196 | srdr.Render(RenderTarget.Screen); 197 | } 198 | 199 | if(Model.SelectedVertices.Count > 0) { 200 | tcrdr.ScreenMode = true; 201 | scrdr.ScreenMode = true; 202 | rcrdr.ScreenMode = true; 203 | 204 | if(CurrentTransformer == TransformerType.TranslationTransformer) 205 | tcrdr.Render(RenderTarget.Screen); 206 | if(CurrentTransformer == TransformerType.ScalingTransformer) 207 | scrdr.Render(RenderTarget.Screen); 208 | if(CurrentTransformer == TransformerType.RotationTransformer) 209 | rcrdr.Render(RenderTarget.Screen); 210 | } 211 | 212 | if(DragInfo.State == DragState.BoxSelect) { 213 | sbrdr.StartPoint = new Vector2( 214 | ((float)DragInfo.StartX) / Viewport.Width, 215 | ((float)DragInfo.StartY) / Viewport.Height); 216 | sbrdr.EndPoint = new Vector2( 217 | ((float)DragInfo.CurrentX) / Viewport.Width, 218 | ((float)DragInfo.CurrentY) / Viewport.Height); 219 | 220 | sbrdr.RenderPlane = true; 221 | sbrdr.Render(RenderTarget.Screen); 222 | } 223 | 224 | if(IsSplitting && PreviousSplittedVertex != null) { 225 | Vector4 v = Vector4.Transform( 226 | new Vector4(PreviousSplittedVertex.Position, 1), 227 | MainCamera.VPMatrix); 228 | v /= v.W; 229 | 230 | sbrdr.StartPoint = v.Xy; 231 | Console.WriteLine(sbrdr.StartPoint); 232 | sbrdr.StartPoint.Y = -sbrdr.StartPoint.Y; 233 | sbrdr.StartPoint = sbrdr.StartPoint / 2 + new Vector2(0.5f, 0.5f); 234 | 235 | sbrdr.EndPoint = new Vector2( 236 | ((float)DragInfo.CurrentX) / Viewport.Width, 237 | ((float)DragInfo.CurrentY) / Viewport.Height); 238 | 239 | sbrdr.RenderPlane = false; 240 | sbrdr.Render(RenderTarget.Screen); 241 | } 242 | 243 | glc.SwapBuffers(); 244 | MarkObjectMap(true); 245 | } 246 | 247 | void UpdateObjectMap() { 248 | if(obj_map_buffer == null) { 249 | obj_map_buffer = new float[Viewport.Height, Viewport.Width, 4]; 250 | MarkObjectMap(true); 251 | } 252 | 253 | if(IsObjectMapDirty()) { 254 | obj_map.Use(); 255 | 256 | GL.ClearColor(0, 0, 0, 0); 257 | GL.Clear(ClearBufferMask.ColorBufferBit); 258 | GL.Clear(ClearBufferMask.DepthBufferBit); 259 | 260 | if(IsWireframe) 261 | crdr.Render(obj_map); 262 | 263 | if(Model.SelectedVertices.Count > 0) { 264 | tcrdr.ScreenMode = false; 265 | scrdr.ScreenMode = false; 266 | rcrdr.ScreenMode = false; 267 | 268 | if(CurrentTransformer == TransformerType.TranslationTransformer) 269 | tcrdr.Render(obj_map); 270 | if(CurrentTransformer == TransformerType.ScalingTransformer) 271 | scrdr.Render(obj_map); 272 | if(CurrentTransformer == TransformerType.RotationTransformer) 273 | rcrdr.Render(obj_map); 274 | } 275 | 276 | GL.BindTexture(TextureTarget.Texture2D, obj_map.TextureColor); 277 | GL.GetTexImage(TextureTarget.Texture2D, 0, 278 | PixelFormat.Rgba, PixelType.Float, obj_map_buffer); 279 | MarkObjectMap(false); 280 | } 281 | } 282 | 283 | void MarkObjectMap(bool dirty) { 284 | if(obj_map_buffer != null) 285 | obj_map_buffer[0,0,3] = dirty ? -1 : 0; 286 | } 287 | 288 | bool IsObjectMapDirty() { 289 | return obj_map_buffer[0,0,3] == -1; 290 | } 291 | 292 | Tuple UnpackVal(float f) { 293 | uint val = BitConverter.ToUInt32(BitConverter.GetBytes(f), 0); 294 | int i1 = (int) (val & 0x00ffffff) - 1; 295 | int i2 = (int) (val >> 28); 296 | 297 | return new Tuple(i1, i2); 298 | } 299 | 300 | ObjectMapElement ObjectMapFuzzy(int x, int y, 301 | int r = 3, int obj_upbound = int.MaxValue) { 302 | int type = 0; 303 | int offx = -r, offy = -r; 304 | 305 | UpdateObjectMap(); 306 | 307 | for(int i = -r; i <= r; i++) 308 | for(int j = -r; j <= r; j++) { 309 | int Y = Viewport.Height-y-1 + i, X = x + j; 310 | int cur_type = UnpackVal(obj_map_buffer[Y, X, 0]).Item2; 311 | 312 | if(cur_type > type && cur_type <= obj_upbound && 313 | offx * offx + offy * offy > i*i + j*j) { 314 | type = cur_type; 315 | offy = i; 316 | offx = j; 317 | } 318 | } 319 | 320 | int max_x = x + offx, max_y = Viewport.Height-y-1 + offy; 321 | 322 | ObjectMapElement me = new ObjectMapElement(); 323 | // in controller map, index-0 means emptiness 324 | var val = UnpackVal(obj_map_buffer[max_y, max_x, 0]); 325 | me.Index = val.Item1; 326 | me.Type = (ObjectType) val.Item2; 327 | me.Position = new Vector3(obj_map_buffer[max_y, max_x, 1], 328 | obj_map_buffer[max_y, max_x, 2], 329 | obj_map_buffer[max_y, max_x, 3]); 330 | 331 | me.ScreenX = max_x; 332 | me.ScreenY = Viewport.Height - max_y - 1; 333 | 334 | return me; 335 | } 336 | 337 | public Size Viewport { 338 | get { return glc.Size; } 339 | } 340 | 341 | //////////////////////////////////////////////////////////////////////// 342 | 343 | // Selection Relevant 344 | public bool KeepSelectionOnClick = false; 345 | public ObjectType BoxSelectingType = ObjectType.None; 346 | 347 | // Dragging Relevant 348 | public class DragInfo_ { 349 | public int StartX = -1; 350 | public int StartY = -1; 351 | public int CurrentX = -1; 352 | public int CurrentY = -1; 353 | public int DeltaX { get { return CurrentX - StartX; } } 354 | public int DeltaY { get { return CurrentY - StartY; } } 355 | 356 | public DragState State = DragState.None; 357 | public object StartInfo; 358 | public bool WasDragging = false; 359 | 360 | public void Reset() { 361 | State = DragState.None; 362 | StartInfo = null; 363 | WasDragging = false; 364 | } 365 | } 366 | public DragInfo_ DragInfo; 367 | 368 | // Misc. 369 | public TransformerType CurrentTransformer = TransformerType.TranslationTransformer; 370 | public ObjectMapElement OpObject; // Object Being Operated 371 | public bool IsSplitting = false; 372 | public MeshVertex PreviousSplittedVertex; 373 | public bool IsWireframe = true; 374 | 375 | //////////////////////////////////////////////////////////////////////// 376 | // Raw Events 377 | 378 | void ModifierKeyDown(Object sender, KeyEventArgs e) { 379 | KeepSelectionOnClick = e.Shift; 380 | } 381 | 382 | void ModifierKeyUp(Object sender, KeyEventArgs e) { 383 | KeepSelectionOnClick = e.Shift; 384 | } 385 | 386 | private void glc_MouseMove(Object sender, MouseEventArgs e) 387 | { 388 | DragState s = DragInfo.State; 389 | 390 | DragInfo.CurrentX = e.X; 391 | DragInfo.CurrentY = e.Y; 392 | 393 | if (DragInfo.CurrentX == DragInfo.StartX && 394 | DragInfo.CurrentY == DragInfo.StartY && 395 | !DragInfo.WasDragging) 396 | return; 397 | 398 | if(s == DragState.None) 399 | return; 400 | 401 | if(s < DragState.RawStates) { 402 | if(s == DragState.LeftButton) { 403 | OpObject = ObjectMapFuzzy(DragInfo.StartX, DragInfo.StartY); 404 | 405 | bool save_selected_pos = true; 406 | 407 | if(BoxSelectingType != ObjectType.None) { 408 | DragInfo.State = DragState.BoxSelect; 409 | } else if(OpObject.Type == ObjectType.TranslationTransformer) { 410 | DragInfo.State = DragState.TranslationTransformer; 411 | } else if(OpObject.Type == ObjectType.ScalingTransformer) { 412 | DragInfo.State = DragState.ScalingTransformer; 413 | } else if(OpObject.Type == ObjectType.RotationTransformer) { 414 | DragInfo.State = DragState.RotationTransformer; 415 | } else { 416 | DragInfo.State = DragState.None; 417 | save_selected_pos = false; 418 | } 419 | 420 | if(save_selected_pos) { 421 | var start_pos = new List>(); 422 | foreach(MeshVertex v in Model.SelectedVertices) 423 | start_pos.Add(new Tuple( 424 | v.Index, v.Position)); 425 | DragInfo.StartInfo = start_pos; 426 | } 427 | } 428 | 429 | if(s == DragState.MiddleButton) { 430 | DragInfo.State = DragState.CameraRotation; 431 | DragInfo.StartInfo = new Transformation(MainCamera.Tf); 432 | } 433 | 434 | if(s == DragState.RightButton) { 435 | DragInfo.State = DragState.CameraTranslation; 436 | DragInfo.StartInfo = new Transformation(MainCamera.Tf); 437 | } 438 | 439 | glc_MouseMove(sender, e); 440 | DragInfo.WasDragging = true; 441 | return; 442 | } 443 | 444 | if(s == DragState.CameraRotation) 445 | CameraRotation_Drag(); 446 | else if(s == DragState.CameraTranslation) 447 | CameraTranslation_Drag(); 448 | else if(s == DragState.TranslationTransformer) 449 | TranslationTransformer_Drag(); 450 | else if(s == DragState.ScalingTransformer) 451 | ScalingTransformer_Drag(); 452 | else if(s == DragState.RotationTransformer) 453 | RotationTransformer_Drag(); 454 | 455 | DragInfo.WasDragging = true; 456 | } 457 | 458 | private void glc_MouseWheel(Object sender, MouseEventArgs e) 459 | { 460 | if(e.Delta > 0) { 461 | MainCamera.Tf.Scale(0.95); 462 | } else { 463 | MainCamera.Tf.Scale(1.05); 464 | } 465 | } 466 | 467 | private void glc_MouseUp(Object sender, MouseEventArgs e) 468 | { 469 | // Was to drag but didn't, treated as clicked 470 | if(!DragInfo.WasDragging) { 471 | // This behavious is usually cancelling actions 472 | if(DragInfo.State == DragState.RightButton) { 473 | IsSplitting = false; 474 | PreviousSplittedVertex = null; 475 | } else if(DragInfo.State == DragState.LeftButton) { 476 | OpObject = ObjectMapFuzzy(e.X, e.Y, 3, (int)ObjectType.ModelVertex); 477 | 478 | TryKeepSelection(); 479 | 480 | if(OpObject.Type == ObjectType.ModelVertex) { 481 | ModelVertex_Click(); 482 | } else if(OpObject.Type == ObjectType.ModelEdge) { 483 | ModelEdge_Click(); 484 | } else if(OpObject.Type == ObjectType.ModelFacet) { 485 | ModelFacet_Click(); 486 | } 487 | } 488 | } else if(DragInfo.State == DragState.None) { 489 | TryKeepSelection(); 490 | } 491 | 492 | if(DragInfo.State == DragState.BoxSelect) { 493 | BoxSelect_Finished(); 494 | this.BoxSelectingType = ObjectType.None; 495 | } 496 | 497 | DragInfo.Reset(); 498 | } 499 | 500 | private void glc_MouseDown(Object sender, MouseEventArgs e) 501 | { 502 | DragInfo.StartX = e.X; 503 | DragInfo.StartY = e.Y; 504 | DragInfo.WasDragging = false; 505 | 506 | if(e.Button == MouseButtons.Middle) { 507 | DragInfo.State = DragState.MiddleButton; 508 | } 509 | 510 | if(e.Button == MouseButtons.Right) { 511 | DragInfo.State = DragState.RightButton; 512 | } 513 | 514 | if(e.Button == MouseButtons.Left) { 515 | DragInfo.State = DragState.LeftButton; 516 | } 517 | } 518 | 519 | void TryKeepSelection() { 520 | if(!KeepSelectionOnClick && Model.SelectedVertices.Count > 0) { 521 | Model.DeselectAll(); 522 | Model.UpdateAll(); 523 | } 524 | } 525 | 526 | //////////////////////////////////////////////////////////////////////// 527 | /// Semantic Events 528 | 529 | void ModelVertex_Click() { 530 | if(IsSplitting) { 531 | if(PreviousSplittedVertex != null) 532 | Model.AddEdge(PreviousSplittedVertex, 533 | Model.Vertices[OpObject.Index], true); 534 | PreviousSplittedVertex = Model.Vertices[OpObject.Index]; 535 | Model.UpdateAll(); 536 | return; 537 | } 538 | 539 | Model.Vertices[OpObject.Index].Selected = true; 540 | Model.UpdateAll(); 541 | } 542 | 543 | void ModelEdge_Click() { 544 | MeshEdge e = Model.Edges[OpObject.Index]; 545 | 546 | if(IsSplitting) { 547 | MeshVertex v = Model.SplitEdgeAt(e, OpObject.Position); 548 | if(PreviousSplittedVertex != null) 549 | Model.AddEdge(PreviousSplittedVertex, v, true); 550 | PreviousSplittedVertex = v; 551 | 552 | Model.UpdateAll(); 553 | return; 554 | } 555 | 556 | e.Selected = true; 557 | e.V1.Selected = true; 558 | e.V2.Selected = true; 559 | Model.UpdateAll(); 560 | 561 | Console.WriteLine(e.V1.Position + "-->" + e.V2.Position); 562 | Console.WriteLine((e.F1 == null ? -1 : e.F1.Index) + ", " + 563 | (e.F2 == null ? -1 : e.F2.Index)); 564 | } 565 | 566 | void ModelFacet_Click() { 567 | Model.Facets[OpObject.Index].Selected = true; 568 | 569 | foreach(MeshEdge fe in Model.Facets[OpObject.Index].Edges) { 570 | fe.Selected = true; 571 | fe.V1.Selected = true; 572 | fe.V2.Selected = true; 573 | } 574 | 575 | Model.UpdateAll(); 576 | } 577 | 578 | void CameraRotation_Drag() { 579 | Transformation tf = new Transformation( 580 | (Transformation)DragInfo.StartInfo); 581 | 582 | Matrix4d m = tf.Matrixd * Matrix4d.Identity; 583 | m.Transpose(); 584 | Vector4d y = Vector4d.Transform(new Vector4d(0, 1, 0, 0), m); 585 | 586 | tf.Translate(0, 0, -5); 587 | tf.RotateAxis(y.Xyz, -DragInfo.DeltaX * (float)Math.PI / 360); 588 | tf.RotateX(-DragInfo.DeltaY * (float)Math.PI / 360); 589 | tf.Translate(0, 0, 5); 590 | MainCamera.Tf = tf; 591 | } 592 | 593 | void CameraTranslation_Drag() { 594 | Transformation tf = new Transformation( 595 | (Transformation)DragInfo.StartInfo); 596 | tf.Translate(-DragInfo.DeltaX / 120.0f, DragInfo.DeltaY / 120.0f, 0); 597 | MainCamera.Tf = tf; 598 | } 599 | 600 | void TranslationTransformer_Drag() { 601 | Vector2 dir = tcrdr.ScreenVector(OpObject.Index); 602 | float dis = Vector2.Dot(new Vector2( 603 | DragInfo.DeltaX, DragInfo.DeltaY), dir); 604 | dis /= 50; 605 | 606 | foreach(var t in (List>)DragInfo.StartInfo) { 607 | Vector3 pos = t.Item2; 608 | pos[OpObject.Index] += dis; 609 | Model.Vertices[t.Item1].Position = pos; 610 | } 611 | 612 | Model.UpdateAll(); 613 | } 614 | 615 | Vector3 CenterFromStartInfo() { 616 | Vector3 center = new Vector3(0, 0, 0); 617 | foreach(var t in (List>)DragInfo.StartInfo) 618 | center += t.Item2; 619 | center /= Model.SelectedVertices.Count; 620 | 621 | return center; 622 | } 623 | 624 | void ScalingTransformer_Drag() { 625 | Vector2 dir = scrdr.ScreenVector(OpObject.Index); 626 | float dis = Vector2.Dot(new Vector2( 627 | DragInfo.DeltaX, DragInfo.DeltaY), dir); 628 | dis /= 50; 629 | 630 | Vector3 center = CenterFromStartInfo(); 631 | 632 | foreach(var t in (List>)DragInfo.StartInfo) { 633 | Vector3 pos = t.Item2; 634 | float delta = pos[OpObject.Index] - center[OpObject.Index]; 635 | pos[OpObject.Index] += dis * delta; 636 | Console.WriteLine(pos); 637 | Model.Vertices[t.Item1].Position = pos; 638 | } 639 | 640 | Model.UpdateAll(); 641 | } 642 | 643 | void RotationTransformer_Drag() { 644 | Vector2 dir = rcrdr.ScreenVector(OpObject.Index, OpObject.Position); 645 | float dis = Vector2.Dot(new Vector2( 646 | DragInfo.DeltaX, DragInfo.DeltaY), dir); 647 | dis /= 50; 648 | 649 | Vector3 center = CenterFromStartInfo(); 650 | 651 | foreach(var t in (List>)DragInfo.StartInfo) { 652 | Vector3 pos = t.Item2; 653 | Vector3 delta = pos - center; 654 | Matrix4 mat = 655 | OpObject.Index == 0 ? Matrix4.CreateRotationX(dis) : 656 | OpObject.Index == 1 ? Matrix4.CreateRotationY(dis) : 657 | OpObject.Index == 2 ? Matrix4.CreateRotationZ(dis) : Matrix4.Identity; 658 | delta = Vector3.Transform(delta, mat); 659 | Console.WriteLine(delta); 660 | pos = center + delta; 661 | 662 | Model.Vertices[t.Item1].Position = pos; 663 | } 664 | 665 | Model.UpdateAll(); 666 | } 667 | 668 | void BoxSelect_Finished() { 669 | UpdateObjectMap(); 670 | 671 | HashSet selected_index_set = new HashSet(); 672 | 673 | for(int i = DragInfo.StartY; i <= DragInfo.CurrentY; i++) 674 | for(int j = DragInfo.StartX; j <= DragInfo.CurrentX; j++) { 675 | int X = j, Y = Viewport.Height - i - 1; 676 | 677 | var val = UnpackVal(obj_map_buffer[Y, X, 0]); 678 | 679 | if(val.Item2 == (int)BoxSelectingType) { 680 | OpObject.Index = val.Item1; 681 | 682 | if(selected_index_set.Contains(OpObject.Index)) 683 | continue; 684 | 685 | if(BoxSelectingType == ObjectType.ModelVertex) { 686 | ModelVertex_Click(); 687 | } else if(BoxSelectingType == ObjectType.ModelEdge) { 688 | ModelEdge_Click(); 689 | } else if(BoxSelectingType == ObjectType.ModelFacet) { 690 | ModelFacet_Click(); 691 | } 692 | 693 | selected_index_set.Add(OpObject.Index); 694 | } 695 | } 696 | 697 | Model.UpdateAll(); 698 | } 699 | 700 | //////////////////////////////////////////////////////////////////////// 701 | /// Main 702 | [STAThread] 703 | static public void Main(string[] args) 704 | { 705 | var win = new MainWindow(); 706 | Application.Run(win); 707 | } 708 | } 709 | } 710 | -------------------------------------------------------------------------------- /CubeSharp/MeshFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using OpenTK; 6 | 7 | namespace CubeSharp 8 | { 9 | public abstract class MeshFactory { 10 | public abstract void AddMeshGraphUpon( 11 | ref MeshGraph mg, bool selected = true); 12 | 13 | public MeshGraph GenerateMeshGraph() { 14 | MeshGraph mg = new MeshGraph(); 15 | AddMeshGraphUpon(ref mg, false); 16 | return mg; 17 | } 18 | 19 | protected void SelectAdjacency(MeshVertex v) { 20 | v.Selected = true; 21 | foreach(MeshEdge e in v.Edges) { 22 | e.Selected = true; 23 | if(e.F1 != null) e.F1.Selected = true; 24 | if(e.F2 != null) e.F2.Selected = true; 25 | } 26 | } 27 | } 28 | 29 | public class BoxMeshFactory : MeshFactory { 30 | public float Length = 2; 31 | public float Width = 2; 32 | public float Height = 2; 33 | 34 | public BoxMeshFactory() { } 35 | public BoxMeshFactory(float l, float w, float h) { 36 | Length = l; Width = w; Height = h; 37 | } 38 | 39 | public override void AddMeshGraphUpon( 40 | ref MeshGraph mg, bool selected = true) { 41 | float l = Length / 2 , w = Width / 2, h = Height / 2; 42 | 43 | MeshVertex[] vs = new MeshVertex[8]; 44 | vs[0] = mg.AddVertex( l, h, w); 45 | vs[1] = mg.AddVertex( l, h, -w); 46 | vs[2] = mg.AddVertex( l, -h, w); 47 | vs[3] = mg.AddVertex( l, -h, -w); 48 | vs[4] = mg.AddVertex(-l, h, w); 49 | vs[5] = mg.AddVertex(-l, h, -w); 50 | vs[6] = mg.AddVertex(-l, -h, w); 51 | vs[7] = mg.AddVertex(-l, -h, -w); 52 | 53 | mg.AddFacet(vs[2], vs[3], vs[1], vs[0]); 54 | mg.AddFacet(vs[4], vs[5], vs[7], vs[6]); 55 | mg.AddFacet(vs[1], vs[5], vs[4], vs[0]); 56 | mg.AddFacet(vs[2], vs[6], vs[7], vs[3]); 57 | mg.AddFacet(vs[0], vs[4], vs[6], vs[2]); 58 | mg.AddFacet(vs[3], vs[7], vs[5], vs[1]); 59 | 60 | if(selected) { 61 | foreach(MeshVertex v in vs) 62 | SelectAdjacency(v); 63 | } 64 | } 65 | 66 | } 67 | 68 | public class ArrowFactory : MeshFactory { 69 | public float Length = 3; 70 | public float HeadSize = 0.5f; 71 | 72 | public override void AddMeshGraphUpon( 73 | ref MeshGraph mg, bool selected = true) { 74 | MeshVertex vorg = mg.AddVertex(0, 0, 0); 75 | MeshVertex vhead = mg.AddVertex(0, 0, Length); 76 | MeshVertex vhead1 = mg.AddVertex( HeadSize / 8, HeadSize / 8, Length - HeadSize); 77 | MeshVertex vhead2 = mg.AddVertex(-HeadSize / 8, HeadSize / 8, Length - HeadSize); 78 | MeshVertex vhead3 = mg.AddVertex(-HeadSize / 8, -HeadSize / 8, Length - HeadSize); 79 | MeshVertex vhead4 = mg.AddVertex( HeadSize / 8, -HeadSize / 8, Length - HeadSize); 80 | 81 | mg.AddEdge(vorg, vhead); 82 | mg.AddFacet(vhead1, vhead2, vhead); 83 | mg.AddFacet(vhead2, vhead3, vhead); 84 | mg.AddFacet(vhead3, vhead4, vhead); 85 | mg.AddFacet(vhead4, vhead1, vhead); 86 | } 87 | } 88 | 89 | public class UVSphereFactory : MeshFactory { 90 | public float Radius = 2; 91 | public int USubdivision = 32; 92 | public int VSubdivision = 16; 93 | 94 | public override void AddMeshGraphUpon( 95 | ref MeshGraph mg, bool selected = true) { 96 | MeshVertex[,] vs = new MeshVertex[VSubdivision - 1,USubdivision]; 97 | for(int v = 1; v < VSubdivision; v++) 98 | for(int u = 0; u < USubdivision; u++) { 99 | double a = Math.PI * 2 * u / USubdivision; 100 | double b = - Math.PI * v / VSubdivision + Math.PI / 2; 101 | vs[v-1,u] = mg.AddVertex( 102 | (float)(Radius * Math.Cos(a) * Math.Cos(b)), 103 | (float)(Radius * Math.Sin(b)), 104 | -(float)(Radius * Math.Sin(a) * Math.Cos(b))); 105 | } 106 | 107 | for(int v = 0; v < VSubdivision - 2; v++) 108 | for(int u = 0; u < USubdivision; u++) { 109 | mg.AddFacet(vs[v,u], vs[v+1,u], 110 | vs[v+1, (u+1) % USubdivision], vs[v,(u+1) % USubdivision]); 111 | } 112 | 113 | MeshVertex top = mg.AddVertex(0, Radius, 0), 114 | btm = mg.AddVertex(0, -Radius, 0); 115 | for(int u = 0; u < USubdivision; u++) { 116 | mg.AddFacet(top, vs[0, u], vs[0,(u+1) % USubdivision]); 117 | mg.AddFacet(vs[VSubdivision - 2,(u+1) % USubdivision], 118 | vs[VSubdivision - 2, u], btm); 119 | } 120 | 121 | foreach(MeshVertex v in vs) 122 | SelectAdjacency(v); 123 | SelectAdjacency(top); 124 | SelectAdjacency(btm); 125 | } 126 | } 127 | 128 | public class PlaneFactory : MeshFactory { 129 | public float Size = 2; 130 | public int USubdivision = 10; 131 | public int VSubdivision = 10; 132 | 133 | public override void AddMeshGraphUpon( 134 | ref MeshGraph mg, bool selected = true) { 135 | MeshVertex[,] vs = new MeshVertex[USubdivision + 1, VSubdivision + 1]; 136 | 137 | for(int i = 0; i <= VSubdivision; i++) 138 | for(int j = 0; j <= USubdivision; j++) { 139 | vs[i,j] = mg.AddVertex(Size * j / USubdivision - Size / 2, 140 | 0, -Size * i / VSubdivision - Size / 2); 141 | } 142 | 143 | for(int i = 0; i < VSubdivision; i++) 144 | for(int j = 0; j < USubdivision; j++) 145 | mg.AddFacet(vs[i,j], vs[i,j+1], vs[i+1,j+1], vs[i+1,j]); 146 | } 147 | } 148 | 149 | public class CylinderFactory : MeshFactory { 150 | public float Height = 2; 151 | public float Radius = 1; 152 | public int Subdivision = 32; 153 | 154 | public override void AddMeshGraphUpon( 155 | ref MeshGraph mg, bool selected = true) { 156 | MeshVertex[] topf = new MeshVertex[Subdivision]; 157 | MeshVertex[] btmf = new MeshVertex[Subdivision]; 158 | 159 | for(int i = 0; i < Subdivision; i++) { 160 | double a = Math.PI * 2 * i / Subdivision; 161 | topf[i] = mg.AddVertex(Radius * (float)Math.Cos(a), 162 | Height / 2, -Radius * (float)Math.Sin(a)); 163 | btmf[i] = mg.AddVertex(Radius * (float)Math.Cos(a), 164 | -Height / 2, -Radius * (float)Math.Sin(a)); 165 | } 166 | 167 | mg.AddFacet(topf); 168 | mg.AddFacet(btmf.Reverse().ToArray()); 169 | 170 | for(int i = 0; i < Subdivision; i++) { 171 | mg.AddFacet(topf[i], btmf[i], 172 | btmf[(i+1)%Subdivision], topf[(i+1)%Subdivision]); 173 | } 174 | } 175 | } 176 | 177 | public class WavefrontFactory : MeshFactory { 178 | public String FileName; 179 | 180 | List vertices; 181 | Vector3 accum_point; 182 | 183 | Vector3 CenterPoint { 184 | get { return accum_point /= vertices.Count; } 185 | } 186 | 187 | MeshVertex ParseVertex(String s, MeshGraph mg) { 188 | Vector3 pos = new Vector3(0, 0, 0); 189 | String[] nums = s.Trim().Split(' ') 190 | .Select(x => x.Trim()) 191 | .Where(x => x.Length > 0).ToArray(); 192 | 193 | if(nums.Length < 3) 194 | throw new Exception("Bad vertex format"); 195 | pos[0] = float.Parse(nums[0]); 196 | pos[1] = float.Parse(nums[1]); 197 | pos[2] = float.Parse(nums[2]); 198 | 199 | if(nums.Length == 4) 200 | pos /= float.Parse(nums[3]); 201 | 202 | accum_point += pos; 203 | 204 | MeshVertex mv = mg.AddVertex(pos); 205 | vertices.Add(mv); 206 | return mv; 207 | } 208 | 209 | MeshFacet ParseFacet(String s, MeshGraph mg) { 210 | String[] items = s.Trim().Split(' ') 211 | .Select(x => x.Trim()) 212 | .Where(x => x.Length > 0).ToArray(); 213 | 214 | List nums = new List(); 215 | 216 | foreach(String i in items) { 217 | String[] snums = i.Split('/'); 218 | nums.Add(int.Parse(snums[0])); 219 | } 220 | 221 | var vs = nums.Select(n => vertices[n - 1]).ToArray(); 222 | try { 223 | return mg.AddFacet(vs); 224 | } catch(Exception) { 225 | try { 226 | return mg.AddFacet(vs.Reverse().ToArray()); 227 | } catch(Exception e2) { 228 | Console.WriteLine(e2.Message); 229 | Console.WriteLine(e2.StackTrace); 230 | } 231 | } 232 | 233 | return null; 234 | } 235 | 236 | public override void AddMeshGraphUpon( 237 | ref MeshGraph mg, bool selected = true) { 238 | StreamReader rd = File.OpenText(FileName); 239 | 240 | vertices = new List(); 241 | accum_point = new Vector3(0, 0, 0); 242 | 243 | while(!rd.EndOfStream) { 244 | String s = rd.ReadLine(); 245 | 246 | if(s.StartsWith("v ")) { 247 | ParseVertex(s.Substring(2), mg); 248 | } 249 | 250 | if(s.StartsWith("f ")) { 251 | ParseFacet(s.Substring(2), mg); 252 | } 253 | } 254 | 255 | foreach(MeshVertex v in vertices) { 256 | SelectAdjacency(v); 257 | } 258 | } 259 | 260 | public void ExportMesh(MeshGraph mg) { 261 | StreamWriter rd = File.CreateText(FileName); 262 | rd.WriteLine("g CubeSharp_Model"); 263 | 264 | foreach(MeshVertex v in mg.Vertices) { 265 | rd.WriteLine("v " + v.Position.X + " " + 266 | v.Position.Y + " " + v.Position.Z); 267 | } 268 | 269 | foreach(MeshFacet f in mg.Facets) { 270 | var n = f.Normal; 271 | 272 | rd.Write("vn "); 273 | rd.Write(n[0] + " " + n[1] + " " + n[2]); 274 | rd.WriteLine(); 275 | 276 | rd.Write("f "); 277 | foreach(MeshVertex v in f.Vertices) { 278 | rd.Write(v.Index + 1); 279 | rd.Write("//"); 280 | rd.Write(f.Index + 1); // vn actually 281 | rd.Write(" "); 282 | } 283 | rd.WriteLine(); 284 | } 285 | 286 | rd.Close(); 287 | } 288 | } 289 | } 290 | 291 | -------------------------------------------------------------------------------- /CubeSharp/MeshGraph.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Diagnostics; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | 7 | using OpenTK; 8 | using OpenTK.Graphics; 9 | using OpenTK.Graphics.OpenGL; 10 | 11 | namespace CubeSharp 12 | { 13 | public class MeshComponent { 14 | private MeshGraph parent; 15 | public MeshGraph Parent { 16 | get { return parent; } 17 | } 18 | 19 | private int index; 20 | public int Index { 21 | get { return index; } 22 | } 23 | 24 | internal void SetGraphInfo(MeshGraph g, int i) { 25 | index = i; 26 | parent = g; 27 | } 28 | } 29 | 30 | public class MeshVertex : MeshComponent { 31 | internal Dictionary adjacency; 32 | 33 | private Vector3 position; 34 | public Vector3 Position { 35 | get { return position; } 36 | set { position = value; } 37 | } 38 | 39 | public bool Selected { 40 | get { return Parent.SelectedVertices.Contains(this); } 41 | set { 42 | if(value) { 43 | Parent.SelectedVertices.Add(this); 44 | } else { 45 | Parent.SelectedVertices.Remove(this); 46 | } 47 | } 48 | } 49 | 50 | public MeshVertex() { 51 | adjacency = new Dictionary(); 52 | position = new Vector3(0, 0, 0); 53 | } 54 | public MeshVertex(Vector3 v) { 55 | adjacency = new Dictionary(); 56 | position = v; 57 | } 58 | public MeshVertex(float x, float y, float z) { 59 | adjacency = new Dictionary(); 60 | position = new Vector3(x, y, z); 61 | } 62 | 63 | public ICollection AdjacencyVertices { 64 | get { return adjacency.Keys; } 65 | } 66 | 67 | public ICollection Edges { 68 | get { return adjacency.Values; } 69 | } 70 | 71 | public IEnumerable AdjacencyFacets { 72 | get { 73 | foreach(var e in Edges) { 74 | if(e.F1 != null) 75 | yield return e.F1; 76 | if(e.F2 != null) 77 | yield return e.F2; 78 | } 79 | } 80 | } 81 | 82 | public MeshEdge EdgeConnecting(MeshVertex v) { 83 | MeshEdge m; 84 | bool success = adjacency.TryGetValue(v, out m); 85 | 86 | if(!success) return null; 87 | else return m; 88 | } 89 | 90 | public void ClearAdjacency() { 91 | List tmp = new List(Edges); 92 | foreach(MeshEdge e in tmp) 93 | Parent.RemoveEdge(e); 94 | } 95 | } 96 | 97 | public class MeshEdge : MeshComponent { 98 | internal MeshVertex p1; 99 | internal MeshVertex p2; 100 | 101 | // An edge has at most two adjacenct facet, 102 | // one is positive directed and the other one is negative 103 | internal MeshFacet f1; // p1 -> p2 104 | internal MeshFacet f2; // p2 -> p1 105 | 106 | public bool Selected { 107 | get { return Parent.SelectedEdges.Contains(this); } 108 | set { 109 | if(value) { 110 | Parent.SelectedEdges.Add(this); 111 | } else { 112 | Parent.SelectedEdges.Remove(this); 113 | } 114 | } 115 | } 116 | 117 | public Tuple VerticesInFacet(MeshFacet f) { 118 | if(f1 == f) 119 | return new Tuple(p1, p2); 120 | if(f2 == f) 121 | return new Tuple(p2, p1); 122 | 123 | return null; 124 | } 125 | 126 | public MeshVertex OppositeVertex(MeshVertex v) { 127 | if(p1 == v) return p2; 128 | else if(p2 == v) return p1; 129 | 130 | throw new Exception("Edge does not contain this vertex."); 131 | } 132 | 133 | public MeshEdge(MeshVertex v1, MeshVertex v2) { 134 | p1 = v1; 135 | p2 = v2; 136 | } 137 | 138 | public MeshVertex V1 { get { return p1; } } 139 | public MeshVertex V2 { get { return p2; } } 140 | public MeshFacet F1 { get { return f1; } } 141 | public MeshFacet F2 { get { return f2; } } 142 | 143 | public void ClearAdjacency() { 144 | if(f1 != null) 145 | Parent.RemoveFacet(F1); 146 | if(f2 != null) 147 | Parent.RemoveFacet(F2); 148 | } 149 | 150 | public IEnumerable Endpoints { 151 | get { yield return V1; yield return V2; } 152 | } 153 | 154 | public IEnumerable AdjacencyFacets { 155 | get { yield return F1; yield return F2; } 156 | } 157 | } 158 | 159 | public class MeshFacet : MeshComponent { 160 | internal List vertices; // important: ordered 161 | 162 | public bool Selected { 163 | get { return Parent.SelectedFacets.Contains(this); } 164 | set { 165 | if(value) { 166 | Parent.SelectedFacets.Add(this); 167 | } else { 168 | Parent.SelectedFacets.Remove(this); 169 | } 170 | } 171 | } 172 | 173 | internal MeshFacet() { 174 | vertices = new List(); 175 | } 176 | 177 | public int TrianglesCount { 178 | get { return vertices.Count - 2; } 179 | } 180 | 181 | public MeshFacet(IEnumerable input) : this() { 182 | vertices = new List(); 183 | foreach(MeshVertex v in input) 184 | vertices.Add(v); 185 | } 186 | 187 | public MeshFacet(params MeshVertex[] input) : 188 | this((IEnumerable)input) { } 189 | 190 | public List Vertices { 191 | get { return vertices; } 192 | } 193 | 194 | public Vector3 Normal { 195 | get { 196 | return Vector3.Cross(vertices[1].Position - vertices[0].Position, 197 | vertices[2].Position - vertices[1].Position).Normalized(); 198 | } 199 | } 200 | 201 | public IEnumerable Edges { 202 | get { 203 | for(int i = 0; i < vertices.Count; i++) { 204 | MeshVertex v = vertices[i]; 205 | MeshVertex next_v = vertices[(i + 1) % vertices.Count]; 206 | MeshEdge edge = v.EdgeConnecting(next_v); 207 | 208 | yield return edge; 209 | } 210 | } 211 | } 212 | } 213 | 214 | //////////////////////////////////////////////////////////////////////////// 215 | 216 | public class MeshGraph { 217 | internal List vertices; 218 | internal List edges; 219 | internal List facets; 220 | 221 | public HashSet SelectedVertices; 222 | public HashSet SelectedEdges; 223 | public HashSet SelectedFacets; 224 | 225 | private int triangles = 0; 226 | public int TrianglesCount { 227 | get { return triangles; } 228 | } 229 | 230 | public List Vertices { get { return vertices; }} 231 | public List Edges { get { return edges; }} 232 | public List Facets { get { return facets; }} 233 | 234 | public MeshGraph() { 235 | vertices = new List(); 236 | edges = new List(); 237 | facets = new List(); 238 | 239 | SelectedVertices = new HashSet(); 240 | SelectedEdges = new HashSet(); 241 | SelectedFacets = new HashSet(); 242 | } 243 | 244 | public MeshVertex AddVertex(float x, float y, float z) { 245 | return AddVertex(new MeshVertex(x, y, z)); 246 | } 247 | 248 | public MeshVertex AddVertex(Vector3 pos) { 249 | return AddVertex(new MeshVertex(pos)); 250 | } 251 | 252 | public MeshVertex AddVertex(MeshVertex v) { 253 | int i = vertices.Count; 254 | vertices.Add(v); 255 | v.SetGraphInfo(this, i); 256 | return v; 257 | } 258 | 259 | public void RemoveVertex(MeshVertex v) { 260 | int i = v.Index; 261 | if(vertices[i] != v) return; 262 | 263 | if(i != vertices.Count - 1) { 264 | vertices[i] = vertices.Last(); 265 | vertices[i].SetGraphInfo(this, i); 266 | } 267 | 268 | vertices.RemoveAt(vertices.Count - 1); 269 | v.Selected = false; 270 | v.ClearAdjacency(); 271 | } 272 | 273 | public MeshEdge AddEdge(MeshVertex p1, MeshVertex p2, 274 | bool check_facet = false) { 275 | MeshEdge e = p1.EdgeConnecting(p2); 276 | if(e != null) return e; 277 | if(p1 == p2) return null; 278 | 279 | e = new MeshEdge(p1, p2); 280 | p1.adjacency.Add(p2, e); 281 | p2.adjacency.Add(p1, e); 282 | 283 | int i = edges.Count; 284 | edges.Add(e); 285 | e.SetGraphInfo(this, i); 286 | 287 | if(check_facet) { 288 | // check the intersection 289 | MeshFacet common_facet = null; 290 | HashSet p1f = new HashSet( 291 | p1.AdjacencyFacets); 292 | foreach(MeshFacet f in p2.AdjacencyFacets) { 293 | if(p1f.Contains(f)) { 294 | common_facet = f; 295 | break; 296 | } 297 | } 298 | 299 | if(common_facet != null) { 300 | List f1 = new List(); 301 | List f2 = new List(); 302 | 303 | List current = f1; 304 | foreach(MeshVertex v in common_facet.Vertices) { 305 | // if current vertex matches p1 or p2, add this vertex 306 | // to both vertex list and switch current to the other one 307 | current.Add(v); 308 | 309 | if(v == p1) { 310 | current = current == f1 ? f2 : f1; 311 | current.Add(p1); 312 | } else if(v == p2) { 313 | current = current == f1 ? f2 : f1; 314 | current.Add(p2); 315 | } 316 | } 317 | 318 | this.RemoveFacet(common_facet); 319 | this.AddFacet(f1.ToArray()); 320 | this.AddFacet(f2.ToArray()); 321 | } 322 | } 323 | 324 | return e; 325 | } 326 | 327 | public void RemoveEdge(MeshEdge e) { 328 | int i = e.Index; 329 | if(edges[i] != e) return; 330 | 331 | if(i != edges.Count - 1) { 332 | edges[i] = edges.Last(); 333 | edges[i].SetGraphInfo(this, i); 334 | } 335 | 336 | edges.RemoveAt(edges.Count - 1); 337 | e.Selected = false; 338 | e.ClearAdjacency(); 339 | e.p1.adjacency.Remove(e.p2); 340 | e.p2.adjacency.Remove(e.p1); 341 | } 342 | 343 | public MeshFacet AddFacet(params MeshVertex[] vs) { 344 | if(vs.Length < 3) 345 | throw new Exception("Vertex count less than 3."); 346 | for(int vi = 0; vi < vs.Length; vi++) { 347 | if(vs[vi] == vs[(vi+1)%vs.Length]) 348 | throw new Exception("Ill-formed vertex sequence"); 349 | } 350 | 351 | MeshFacet f = new MeshFacet(vs); 352 | 353 | string failure = ""; 354 | for(int vi = 0; vi < vs.Length; vi++) { 355 | MeshVertex p1 = vs[vi]; 356 | MeshVertex p2 = vs[(vi + 1) % vs.Length]; 357 | 358 | MeshEdge e = AddEdge(p1, p2); 359 | 360 | if(e.V1 == p1 && e.V2 == p2) { 361 | if(e.f1 != null) { 362 | failure = "Positive facet has been occupied"; 363 | break; 364 | } 365 | e.f1 = f; 366 | } else if(e.V1 == p2 && e.V2 == p1) { 367 | if(e.f2 != null) { 368 | failure = "Negative facet has been occupied"; 369 | break; 370 | } 371 | e.f2 = f; 372 | } else 373 | throw new Exception("Unexpected edge"); 374 | } 375 | 376 | // rollback 377 | if(failure.Length > 0) { 378 | foreach(MeshEdge e in f.Edges) { 379 | if(e == null) continue; 380 | if(e.f1 == f) e.f1 = null; 381 | if(e.f2 == f) e.f2 = null; 382 | } 383 | throw new Exception(failure); 384 | } 385 | 386 | int i = facets.Count; 387 | facets.Add(f); 388 | f.SetGraphInfo(this, i); 389 | 390 | triangles += f.TrianglesCount; 391 | 392 | return f; 393 | } 394 | 395 | public void RemoveFacet(MeshFacet f) { 396 | int i = f.Index; 397 | if(facets[i] != f) return; 398 | 399 | triangles -= f.TrianglesCount; 400 | 401 | if(i != facets.Count - 1) { 402 | facets[i] = facets.Last(); 403 | facets[i].SetGraphInfo(this, i); 404 | } 405 | 406 | facets.RemoveAt(facets.Count - 1); 407 | f.Selected = false; 408 | foreach(MeshEdge e in f.Edges) { 409 | if(e.f1 == f) 410 | e.f1 = null; 411 | else if(e.f2 == f) 412 | e.f2 = null; 413 | else 414 | throw new Exception("Unexpected edge"); 415 | } 416 | } 417 | 418 | public MeshVertex Eqv(MeshVertex v) { 419 | if(v == null) return null; 420 | return vertices[v.Index]; 421 | } 422 | 423 | public MeshEdge Eqv(MeshEdge e) { 424 | if(e == null) return null; 425 | return edges[e.Index]; 426 | } 427 | 428 | public MeshFacet Eqv(MeshFacet f) { 429 | if(f == null) return null; 430 | return facets[f.Index]; 431 | } 432 | 433 | public MeshGraph Clone() { 434 | MeshGraph mg = new MeshGraph(); 435 | mg.triangles = this.triangles; 436 | 437 | foreach(MeshVertex v in vertices) { 438 | MeshVertex new_v = new MeshVertex(v.Position); 439 | new_v.SetGraphInfo(mg, mg.vertices.Count); 440 | mg.vertices.Add(new_v); 441 | } 442 | 443 | foreach(MeshEdge e in edges) { 444 | MeshEdge new_e = new MeshEdge(mg.Eqv(e.V1), mg.Eqv(e.V2)); 445 | new_e.SetGraphInfo(mg, mg.edges.Count); 446 | mg.edges.Add(new_e); 447 | } 448 | 449 | foreach(MeshFacet f in facets) { 450 | MeshFacet new_f = new MeshFacet(); 451 | foreach(MeshVertex v in f.vertices) 452 | new_f.vertices.Add(mg.Eqv(v)); 453 | new_f.SetGraphInfo(mg, mg.facets.Count); 454 | mg.facets.Add(new_f); 455 | } 456 | 457 | // Maintain adjacency cache 458 | foreach(MeshEdge e in mg.edges) { 459 | MeshEdge old_e = this.Eqv(e); 460 | e.f1 = mg.Eqv(old_e.f1); 461 | e.f2 = mg.Eqv(old_e.f2); 462 | } 463 | 464 | foreach(MeshVertex v in mg.vertices) { 465 | MeshVertex old_v = this.Eqv(v); 466 | foreach(var entry in old_v.adjacency) { 467 | v.adjacency.Add(mg.Eqv(entry.Key), mg.Eqv(entry.Value)); 468 | } 469 | } 470 | 471 | return mg; 472 | } 473 | 474 | //////////////////////////////////////////////////////////////////////// 475 | 476 | public void DeselectAll() { 477 | SelectedVertices.Clear(); 478 | SelectedEdges.Clear(); 479 | SelectedFacets.Clear(); 480 | } 481 | 482 | public void UpdateAll() { 483 | VertexData.UpdateData(); 484 | EdgeData.UpdateData(); 485 | FacetData.UpdateData(); 486 | } 487 | 488 | public Vector3 SelectedVerticesMidpoint { 489 | get { 490 | Vector3 avg = new Vector3(0, 0, 0); 491 | foreach(MeshVertex v in SelectedVertices) 492 | avg += v.Position; 493 | avg /= SelectedVertices.Count; 494 | return avg; 495 | } 496 | } 497 | 498 | //////////////////////////////////////////////////////////////////////// 499 | 500 | public MeshVertex SplitEdgeAt(MeshEdge e, Vector3 pos) { 501 | if(Edges[e.Index] != e) 502 | return null; 503 | 504 | MeshVertex new_v = this.AddVertex(pos); 505 | 506 | // save the vertex sequences of adjacency facets 507 | List f1v = new List(); 508 | List f2v = new List(); 509 | 510 | if(e.F1 != null) { 511 | for(int i = 0; i < e.F1.Vertices.Count; i++) { 512 | var vs = e.F1.Vertices; 513 | f1v.Add(vs[i]); 514 | MeshEdge cur_e = vs[i].EdgeConnecting(vs[(i+1)%vs.Count]); 515 | if(cur_e == e) f1v.Add(new_v); 516 | } 517 | } 518 | 519 | if(e.F2 != null) { 520 | for(int i = 0; i < e.F2.Vertices.Count; i++) { 521 | var vs = e.F2.Vertices; 522 | f2v.Add(vs[i]); 523 | MeshEdge cur_e = vs[i].EdgeConnecting(vs[(i+1)%vs.Count]); 524 | if(cur_e == e) f2v.Add(new_v); 525 | } 526 | } 527 | 528 | // reconstruct facet 529 | this.RemoveEdge(e); 530 | if(f1v.Count > 0) this.AddFacet(f1v.ToArray()); 531 | if(f2v.Count > 0) this.AddFacet(f2v.ToArray()); 532 | 533 | return new_v; 534 | } 535 | 536 | public MeshFacet AddTriangle(Vector3 posdir, params MeshVertex[] vs) { 537 | // There should always be one edge that has connected with 538 | // another facet to create a correct facet, otherwise the 539 | // algorithm will create the facet corresponding to posdir 540 | bool has_connected_edge = false; 541 | 542 | // After sorting, vs[0] -> vs[1] -> vs[2] is a positive order 543 | for(int i = 0; i < 2; i++) 544 | for(int j = i + 1; j < 2; j++) { 545 | MeshEdge e = vs[i].EdgeConnecting(vs[j]); 546 | 547 | if(e == null) 548 | continue; 549 | 550 | if(e.F1 != null) { 551 | has_connected_edge = true; 552 | 553 | MeshVertex[] sorted_vs = new MeshVertex[3]; 554 | sorted_vs[0] = e.V2; 555 | sorted_vs[1] = e.V1; 556 | sorted_vs[2] = vs[3-i-j]; 557 | vs = sorted_vs; 558 | 559 | break; 560 | } 561 | 562 | if(e.F2 != null) { 563 | has_connected_edge = true; 564 | 565 | MeshVertex[] sorted_vs = new MeshVertex[3]; 566 | sorted_vs[0] = e.V1; 567 | sorted_vs[1] = e.V2; 568 | sorted_vs[2] = vs[3-i-j]; 569 | vs = sorted_vs; 570 | 571 | break; 572 | } 573 | } 574 | 575 | if(!has_connected_edge) { 576 | Vector3 normal = Vector3.Cross( 577 | vs[1].Position - vs[0].Position, 578 | vs[2].Position - vs[1].Position); 579 | 580 | if(Vector3.Dot(posdir, normal) < 0) { 581 | MeshVertex[] sorted_vs = new MeshVertex[3]; 582 | sorted_vs[0] = vs[2]; 583 | sorted_vs[1] = vs[1]; 584 | sorted_vs[2] = vs[0]; 585 | vs = sorted_vs; 586 | } 587 | } 588 | 589 | return this.AddFacet(vs); 590 | } 591 | 592 | //////////////////////////////////////////////////////////////////////// 593 | 594 | MeshFacetData mfd; 595 | MeshFacetDataWithNormal mfdwn; 596 | MeshEdgeData med; 597 | MeshVertexData mvd; 598 | 599 | public MeshFacetData FacetData { 600 | get { 601 | if(mfd == null) 602 | mfd = new MeshFacetData(this); 603 | return mfd; 604 | } 605 | } 606 | 607 | public MeshFacetDataWithNormal FacetDataWithNormal { 608 | get { 609 | if(mfdwn == null) 610 | mfdwn = new MeshFacetDataWithNormal(this); 611 | return mfdwn; 612 | } 613 | } 614 | 615 | public MeshEdgeData EdgeData { 616 | get { 617 | if(med == null) 618 | med = new MeshEdgeData(this); 619 | return med; 620 | } 621 | } 622 | 623 | public MeshVertexData VertexData { 624 | get { 625 | if(mvd == null) 626 | mvd = new MeshVertexData(this); 627 | return mvd; 628 | } 629 | } 630 | } 631 | 632 | public abstract class MeshDataBase { 633 | private int gl_buffer = 0; 634 | private int gl_buffer_size = 0; 635 | private int gl_attrib = 0; 636 | 637 | private int[] layouts; 638 | private int layout_len; 639 | 640 | protected int[] Layouts { 641 | get { return layouts; } 642 | } 643 | 644 | protected int LayoutLength { 645 | get { return layout_len; } 646 | } 647 | 648 | private int count = 0; 649 | public int Count { 650 | get { return count; } 651 | } 652 | 653 | public MeshDataBase(params int[] ly) { 654 | layouts = ly; 655 | layout_len = layouts.Sum(); 656 | } 657 | 658 | public int GLBuffer { 659 | get { 660 | if(gl_buffer == 0) 661 | gl_buffer = GL.GenBuffer(); 662 | 663 | return gl_buffer; 664 | } 665 | } 666 | 667 | public int GLAttrib { 668 | get { 669 | if(gl_attrib == 0) { 670 | gl_attrib = GL.GenVertexArray(); 671 | 672 | GL.BindVertexArray(gl_attrib); 673 | GL.BindBuffer(BufferTarget.ArrayBuffer, GLBuffer); 674 | 675 | int off = 0; 676 | for(int i = 0; i < layouts.Length; i++) { 677 | GL.EnableVertexAttribArray(i); 678 | GL.VertexAttribPointer(i, layouts[i], 679 | VertexAttribPointerType.Float, true, 680 | LayoutLength * sizeof(float), 681 | off * sizeof(float)); 682 | off += layouts[i]; 683 | } 684 | } 685 | 686 | return gl_attrib; 687 | } 688 | } 689 | 690 | private IntPtr data = IntPtr.Zero; 691 | private int offset = 0; 692 | 693 | protected void StartPushing(int count) { 694 | this.count = count; 695 | int size = count * LayoutLength * sizeof(float); 696 | 697 | unsafe { 698 | if(gl_buffer_size < size) { 699 | int log = (int) Math.Floor(Math.Log(size, 2)) + 1; 700 | int new_size = (int) Math.Pow(2, log); 701 | 702 | GL.BindBuffer(BufferTarget.ArrayBuffer, GLBuffer); 703 | GL.BufferData(BufferTarget.ArrayBuffer, new IntPtr(new_size), 704 | new IntPtr(null), BufferUsageHint.StaticDraw); 705 | 706 | gl_buffer_size = new_size; 707 | } 708 | 709 | GL.BindBuffer(BufferTarget.ArrayBuffer, GLBuffer); 710 | data = new IntPtr((void*) GL.MapBuffer( 711 | BufferTarget.ArrayBuffer, BufferAccess.WriteOnly)); 712 | 713 | Seek(0); 714 | } 715 | } 716 | 717 | protected void StopPushing() { 718 | unsafe { 719 | GL.BindBuffer(BufferTarget.ArrayBuffer, GLBuffer); 720 | GL.UnmapBuffer(BufferTarget.ArrayBuffer); 721 | data = IntPtr.Zero; 722 | } 723 | } 724 | 725 | int current_layout = 0; 726 | 727 | protected void Seek(int pos) { 728 | if(pos * LayoutLength * sizeof(float) >= gl_buffer_size) 729 | throw new Exception("Seek Out Bound"); 730 | 731 | offset = pos * LayoutLength; 732 | current_layout = 0; 733 | } 734 | 735 | protected void PushData(params float[] floatdata) { 736 | unsafe { 737 | if(data == IntPtr.Zero) 738 | throw new Exception("Did not start pushing."); 739 | if(floatdata.Length != layouts[current_layout]) 740 | throw new Exception("No such layout."); 741 | 742 | float* ptr = ((float*) data.ToPointer()) + offset; 743 | for(int i = 0; i < floatdata.Length; i++) { 744 | ptr[i] = floatdata[i]; 745 | } 746 | 747 | offset += floatdata.Length; 748 | current_layout = (current_layout + 1) % layouts.Length; 749 | } 750 | } 751 | 752 | public abstract void UpdateData(); 753 | } 754 | 755 | public class MeshFacetData : MeshDataBase { 756 | 757 | MeshGraph Mesh; 758 | 759 | public MeshFacetData() : base(4, 1) { } 760 | public MeshFacetData(MeshGraph mg) : this() { 761 | Mesh = mg; 762 | } 763 | 764 | public override void UpdateData() { 765 | StartPushing(Mesh.TrianglesCount * 3); 766 | 767 | foreach(MeshFacet f in Mesh.Facets) { 768 | for(int i = 0; i < f.TrianglesCount; i++) { 769 | // Iteration Count: this.TrianglesCount 770 | 771 | MeshVertex v1 = f.vertices[0]; 772 | MeshVertex v2 = f.vertices[i + 1]; 773 | MeshVertex v3 = f.vertices[i + 2]; 774 | 775 | PushData(v1.Position[0], v1.Position[1], v1.Position[2], f.Index); 776 | PushData(f.Selected ? 1 : 0); 777 | 778 | PushData(v2.Position[0], v2.Position[1], v2.Position[2], f.Index); 779 | PushData(f.Selected ? 1 : 0); 780 | 781 | PushData(v3.Position[0], v3.Position[1], v3.Position[2], f.Index); 782 | PushData(f.Selected ? 1 : 0); 783 | } 784 | } 785 | 786 | StopPushing(); 787 | } 788 | } 789 | 790 | public class MeshFacetDataWithNormal : MeshDataBase { 791 | 792 | MeshGraph Mesh; 793 | 794 | public MeshFacetDataWithNormal() : base(4, 3) { } 795 | public MeshFacetDataWithNormal(MeshGraph mg) : this() { 796 | Mesh = mg; 797 | } 798 | 799 | public override void UpdateData() { 800 | StartPushing(Mesh.TrianglesCount * 3); 801 | 802 | foreach(MeshFacet f in Mesh.Facets) { 803 | Vector3 n = f.Normal; 804 | for(int i = 0; i < f.TrianglesCount; i++) { 805 | // Iteration Count: this.TrianglesCount 806 | 807 | MeshVertex v1 = f.vertices[0]; 808 | MeshVertex v2 = f.vertices[i + 1]; 809 | MeshVertex v3 = f.vertices[i + 2]; 810 | 811 | PushData(v1.Position[0], v1.Position[1], v1.Position[2], f.Index); 812 | PushData(n[0], n[1], n[2]); 813 | 814 | PushData(v2.Position[0], v2.Position[1], v2.Position[2], f.Index); 815 | PushData(n[0], n[1], n[2]); 816 | 817 | PushData(v3.Position[0], v3.Position[1], v3.Position[2], f.Index); 818 | PushData(n[0], n[1], n[2]); 819 | } 820 | } 821 | 822 | StopPushing(); 823 | } 824 | } 825 | 826 | public class MeshEdgeData : MeshDataBase { 827 | MeshGraph Mesh; 828 | 829 | public MeshEdgeData() : base(4, 4) { } 830 | public MeshEdgeData(MeshGraph mg) : this() { 831 | Mesh = mg; 832 | } 833 | 834 | public override void UpdateData() { 835 | StartPushing(Mesh.Edges.Count * 2); 836 | 837 | foreach(MeshEdge e in Mesh.Edges) { 838 | PushData(e.V1.Position[0], e.V1.Position[1], e.V1.Position[2], e.Index); 839 | PushData(e.Selected ? 1 : 0, 0, 0, 0); 840 | 841 | PushData(e.V2.Position[0], e.V2.Position[1], e.V2.Position[2], e.Index); 842 | PushData(e.Selected ? 1 : 0, 0, 0, 0); 843 | } 844 | 845 | StopPushing(); 846 | } 847 | } 848 | 849 | public class MeshVertexData : MeshDataBase { 850 | MeshGraph Mesh; 851 | 852 | public MeshVertexData() : base(4, 4) { } 853 | public MeshVertexData(MeshGraph mg) : this() { 854 | Mesh = mg; 855 | } 856 | 857 | public override void UpdateData() { 858 | StartPushing(Mesh.Vertices.Count); 859 | 860 | foreach(MeshVertex v in Mesh.Vertices) { 861 | PushData(v.Position[0], v.Position[1], v.Position[2], v.Index); 862 | PushData(v.Selected ? 1 : 0, 0, 0, 0); 863 | } 864 | 865 | StopPushing(); 866 | } 867 | } 868 | } 869 | 870 | -------------------------------------------------------------------------------- /CubeSharp/Renderer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using System.Diagnostics; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | 7 | using OpenTK; 8 | using OpenTK.Graphics; 9 | using OpenTK.Graphics.OpenGL; 10 | 11 | namespace CubeSharp 12 | { 13 | public class Transformation { 14 | 15 | public void Translate(double x, double y, double z) 16 | { 17 | matrix = Matrix4d.CreateTranslation(x, y, z) * matrix; 18 | inverse_matrix *= Matrix4d.CreateTranslation(-x, -y, -z); 19 | } 20 | 21 | public void Translate(Vector3d tl) 22 | { 23 | matrix = Matrix4d.CreateTranslation(tl) * matrix; 24 | inverse_matrix *= Matrix4d.CreateTranslation(-tl); 25 | } 26 | 27 | public void RotateX(double angle) 28 | { 29 | matrix = Matrix4d.CreateRotationX(angle) * matrix; 30 | inverse_matrix *= Matrix4d.CreateRotationX(-angle); 31 | } 32 | 33 | public void RotateY(double angle) 34 | { 35 | matrix = Matrix4d.CreateRotationY(angle) * matrix; 36 | inverse_matrix *= Matrix4d.CreateRotationY(-angle); 37 | } 38 | 39 | public void RotateZ(double angle) 40 | { 41 | matrix = Matrix4d.CreateRotationZ(angle) * matrix; 42 | inverse_matrix *= Matrix4d.CreateRotationZ(-angle); 43 | } 44 | 45 | public void RotateAxis(Vector3d axis, double angle) 46 | { 47 | matrix = Matrix4d.CreateFromAxisAngle(axis, angle) * matrix; 48 | inverse_matrix *= Matrix4d.CreateFromAxisAngle(axis, -angle); 49 | } 50 | 51 | public void Scale(double s) 52 | { 53 | matrix *= Matrix4d.Scale(s); 54 | inverse_matrix = Matrix4d.Scale(1 / s) * inverse_matrix; 55 | } 56 | 57 | private Matrix4d matrix; 58 | public Matrix4 Matrix { 59 | get { return Matrix4dToMatrix4(matrix); } 60 | } 61 | public Matrix4d Matrixd { 62 | get { return matrix; } 63 | } 64 | 65 | private Matrix4d inverse_matrix; 66 | public Matrix4 InverseMatrix { 67 | get { return Matrix4dToMatrix4(inverse_matrix); } 68 | } 69 | public Matrix4d InverseMatrixd { 70 | get { return inverse_matrix; } 71 | } 72 | 73 | public Transformation() { 74 | matrix = Matrix4d.Identity; 75 | inverse_matrix = Matrix4d.Identity; 76 | } 77 | 78 | public Transformation(Transformation other) { 79 | matrix = other.matrix * Matrix4d.Identity; 80 | inverse_matrix = other.inverse_matrix * Matrix4d.Identity; 81 | } 82 | 83 | static public Matrix4 Matrix4dToMatrix4(Matrix4d m) { 84 | Matrix4 new_m = new Matrix4(); 85 | new_m.M11 = (float) m.M11; new_m.M12 = (float) m.M12; new_m.M13 = (float) m.M13; new_m.M14 = (float) m.M14; 86 | new_m.M21 = (float) m.M21; new_m.M22 = (float) m.M22; new_m.M23 = (float) m.M23; new_m.M24 = (float) m.M24; 87 | new_m.M31 = (float) m.M31; new_m.M32 = (float) m.M32; new_m.M33 = (float) m.M33; new_m.M34 = (float) m.M34; 88 | new_m.M41 = (float) m.M41; new_m.M42 = (float) m.M42; new_m.M43 = (float) m.M43; new_m.M44 = (float) m.M44; 89 | return new_m; 90 | } 91 | 92 | static public Matrix4d Matrix4ToMatrix4d(Matrix4 m) { 93 | Matrix4d new_m = new Matrix4d(); 94 | new_m.M11 = m.M11; new_m.M12 = m.M12; new_m.M13 = m.M13; new_m.M14 = m.M14; 95 | new_m.M21 = m.M21; new_m.M22 = m.M22; new_m.M23 = m.M23; new_m.M24 = m.M24; 96 | new_m.M31 = m.M31; new_m.M32 = m.M32; new_m.M33 = m.M33; new_m.M34 = m.M34; 97 | new_m.M41 = m.M41; new_m.M42 = m.M42; new_m.M43 = m.M43; new_m.M44 = m.M44; 98 | return new_m; 99 | } 100 | } 101 | 102 | public class RenderTarget { 103 | private int framebuffer = -1; 104 | private int texture_color = -1; 105 | private int texture_depth = -1; 106 | 107 | Size size; 108 | 109 | public Size Size { 110 | get { return size; } 111 | set { 112 | if(framebuffer > 0) { 113 | GL.DeleteFramebuffer(framebuffer); 114 | GL.DeleteTexture(texture_color); 115 | GL.DeleteTexture(texture_depth); 116 | 117 | framebuffer = -1; 118 | texture_color = -1; 119 | texture_depth = -1; 120 | } 121 | 122 | size = value; 123 | } 124 | } 125 | 126 | PixelType type; 127 | 128 | public RenderTarget(PixelType t, Size sz) { 129 | type = t; 130 | size = sz; 131 | } 132 | 133 | private void init() { 134 | framebuffer = GL.GenFramebuffer(); 135 | texture_color = GL.GenTexture(); 136 | texture_depth = GL.GenTexture(); 137 | 138 | GL.BindTexture(TextureTarget.Texture2D, texture_color); 139 | if(type == PixelType.Byte) 140 | GL.TexImage2D(TextureTarget.Texture2D, 0, 141 | PixelInternalFormat.Rgba8, size.Width, size.Height, 0, 142 | PixelFormat.Rgba, PixelType.Byte, (IntPtr)0); 143 | else if(type == PixelType.Float) 144 | GL.TexImage2D(TextureTarget.Texture2D, 0, 145 | PixelInternalFormat.Rgba32f, size.Width, size.Height, 0, 146 | PixelFormat.Rgba, PixelType.Float, (IntPtr)0); 147 | GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear); 148 | GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear); 149 | GL.GenerateMipmap(GenerateMipmapTarget.Texture2D); 150 | 151 | GL.BindTexture(TextureTarget.Texture2D, texture_depth); 152 | GL.TexImage2D(TextureTarget.Texture2D, 0, 153 | PixelInternalFormat.DepthComponent32f, size.Width, size.Height, 0, 154 | PixelFormat.DepthComponent, PixelType.Float, (IntPtr)0); 155 | 156 | GL.BindFramebuffer(FramebufferTarget.Framebuffer, framebuffer); 157 | GL.FramebufferTexture(FramebufferTarget.Framebuffer, 158 | FramebufferAttachment.ColorAttachment0, 159 | texture_color, 0); 160 | GL.FramebufferTexture(FramebufferTarget.Framebuffer, 161 | FramebufferAttachment.DepthAttachment, 162 | texture_depth, 0); 163 | } 164 | 165 | public int Framebuffer { 166 | get { 167 | if(framebuffer == -1) init(); 168 | return framebuffer; 169 | } 170 | } 171 | 172 | public int TextureColor { 173 | get { 174 | if(IsScreen) 175 | throw new Exception("Cannot get screen color"); 176 | if(texture_color == -1) init(); 177 | return texture_color; 178 | } 179 | } 180 | 181 | public int TextureDepth { 182 | get { 183 | if(IsScreen) 184 | throw new Exception("Cannot get screen depth"); 185 | if(texture_depth == -1) init(); 186 | return texture_depth; 187 | } 188 | } 189 | 190 | public bool IsScreen { 191 | get { return framebuffer == 0; } 192 | } 193 | 194 | static RenderTarget screen; 195 | static public RenderTarget Screen { 196 | get { 197 | if(screen == null || screen.framebuffer != 0) { 198 | screen = new RenderTarget(PixelType.Byte, new Size(0, 0)); 199 | screen.framebuffer = 0; 200 | } 201 | 202 | return screen; 203 | } 204 | } 205 | 206 | public void Use() { 207 | GL.BindFramebuffer(FramebufferTarget.Framebuffer, Framebuffer); 208 | } 209 | } 210 | 211 | public abstract class RendererBase { 212 | string vertex_shader_source; 213 | string fragment_shader_source; 214 | string geometry_shader_source; 215 | 216 | int vertex_shader = 0; 217 | int fragment_shader = 0; 218 | int geometry_shader = 0; 219 | int program = 0; 220 | 221 | public int Program { 222 | get { 223 | if(program != 0) 224 | return program; 225 | if(vertex_shader_source.Length == 0) 226 | throw new Exception("No Vertex Source"); 227 | if(fragment_shader_source.Length == 0) 228 | throw new Exception("No Fragment Source"); 229 | 230 | vertex_shader = GL.CreateShader(ShaderType.VertexShader); 231 | GL.ShaderSource(vertex_shader, vertex_shader_source); 232 | GL.CompileShader(vertex_shader); 233 | string vs_log = GL.GetShaderInfoLog(vertex_shader); 234 | if(vs_log.Length > 0) 235 | Console.WriteLine("Vertex Comp: " + vs_log); 236 | 237 | fragment_shader = GL.CreateShader(ShaderType.FragmentShader); 238 | GL.ShaderSource(fragment_shader, fragment_shader_source); 239 | GL.CompileShader(fragment_shader); 240 | string fs_log = GL.GetShaderInfoLog(fragment_shader); 241 | if(fs_log.Length > 0) 242 | Console.WriteLine("Fragment Comp: " + fs_log); 243 | 244 | if(geometry_shader_source.Length > 0) { 245 | geometry_shader = GL.CreateShader(ShaderType.GeometryShader); 246 | GL.ShaderSource(geometry_shader, geometry_shader_source); 247 | GL.CompileShader(geometry_shader); 248 | string gs_log = GL.GetShaderInfoLog(geometry_shader); 249 | if(gs_log.Length > 0) 250 | Console.WriteLine("Geometry Comp: " + gs_log); 251 | } 252 | 253 | program = GL.CreateProgram(); 254 | GL.AttachShader(program, vertex_shader); 255 | GL.AttachShader(program, fragment_shader); 256 | if(geometry_shader != 0) 257 | GL.AttachShader(program, geometry_shader); 258 | GL.LinkProgram(program); 259 | string ln_log = GL.GetProgramInfoLog(program); 260 | if(ln_log.Length > 0) 261 | Console.WriteLine("Link: " + ln_log); 262 | 263 | return program; 264 | } 265 | } 266 | 267 | protected void SetSource(string vsrc, string gsrc, string fsrc) { 268 | vertex_shader_source = vsrc; 269 | geometry_shader_source = gsrc; 270 | fragment_shader_source = fsrc; 271 | } 272 | 273 | public abstract void Render(RenderTarget rt); 274 | 275 | Dictionary uniforms; 276 | public int Uniform(string name) { 277 | if(uniforms == null) 278 | uniforms = new Dictionary(); 279 | 280 | int id; 281 | bool suc = uniforms.TryGetValue(name, out id); 282 | 283 | if(!suc) { 284 | GL.GetUniformIndices(Program, 1, 285 | new string[] { name }, out id); 286 | uniforms[name] = id; 287 | } 288 | 289 | return id; 290 | } 291 | } 292 | 293 | public class Camera { 294 | public Transformation Tf; 295 | 296 | double view_angle = Math.PI / 2; 297 | public double ViewAngle { 298 | get { return view_angle; } 299 | set { 300 | if(value < Math.PI - 0.01) 301 | view_angle = value; 302 | else 303 | view_angle = Math.PI - 0.01; 304 | } 305 | } 306 | 307 | double ratio = 1; 308 | public double Ratio { 309 | get { return ratio; } 310 | set { ratio = value; } 311 | } 312 | 313 | public Camera() { 314 | Tf = new Transformation(); 315 | } 316 | 317 | public Vector3 Position { 318 | get { 319 | return Tf.Matrix.ExtractTranslation(); 320 | } 321 | } 322 | 323 | public Matrix4 VPMatrix { 324 | get { 325 | Matrix4 m = Tf.InverseMatrix; 326 | m *= Matrix4.CreatePerspectiveFieldOfView( 327 | (float)ViewAngle, (float) ratio, 0.1f, 100); 328 | return m; 329 | } 330 | } 331 | 332 | public Matrix4d VPMatrixd { 333 | get { 334 | Matrix4d m = Tf.InverseMatrixd; 335 | m *= Matrix4d.CreatePerspectiveFieldOfView( 336 | (float)ViewAngle, ratio, 0.1f, 100); 337 | return m; 338 | } 339 | } 340 | } 341 | 342 | public abstract class RendererWithCamera : RendererBase { 343 | public Camera Camera; 344 | Matrix4 prev_mat; 345 | 346 | public RendererWithCamera() { } 347 | 348 | protected void UpdateCamera(string name) { 349 | GL.UseProgram(Program); 350 | 351 | Matrix4 vp_mat = Camera.VPMatrix; 352 | if(prev_mat != vp_mat) { 353 | GL.UniformMatrix4(Uniform(name), false, ref vp_mat); 354 | prev_mat = vp_mat; 355 | } 356 | 357 | } 358 | } 359 | 360 | public enum DrawMode { 361 | Vertex = 1, Edge = 2, Facet = 4, All = 7, 362 | } 363 | 364 | public class WireframeRenderer : RendererWithCamera { 365 | string vertex_shader_source = @" 366 | #version 330 core 367 | 368 | uniform mat4 vp; 369 | 370 | layout(location = 0) in vec4 position; 371 | layout(location = 1) in vec4 info; 372 | 373 | out float selected; 374 | 375 | void main() 376 | { 377 | vec4 p = vec4(position.xyz, 1); 378 | gl_Position = vp * p; 379 | selected = info.x; 380 | }"; 381 | 382 | string fragment_shader_source = @" 383 | #version 330 core 384 | 385 | uniform ivec4 mode; 386 | 387 | in float selected; 388 | out vec4 outColor; 389 | 390 | void main() 391 | { 392 | if(mode.x == 3) 393 | outColor = vec4(1.0, 1.0, 1.0, 1); 394 | else if(mode.x == 2) 395 | outColor = vec4(0.8, 0.8, 0.8, 1); 396 | else if(mode.x == 1) 397 | outColor = vec4(0.5, 0.5, 0.5, 1); 398 | else 399 | outColor = vec4(0, 0, 0, 1); 400 | 401 | if(selected > 0.99) { 402 | outColor.rgb = outColor.rgb * 0.3; 403 | outColor.r = 1; 404 | } 405 | }"; 406 | 407 | public MeshGraph Model; 408 | 409 | DrawMode Mode = DrawMode.All; 410 | 411 | public WireframeRenderer() 412 | { 413 | SetSource(vertex_shader_source, "", fragment_shader_source); 414 | } 415 | 416 | public override void Render(RenderTarget rt) { 417 | rt.Use(); 418 | GL.UseProgram(Program); 419 | UpdateCamera("vp"); 420 | 421 | GL.Disable(EnableCap.CullFace); 422 | 423 | GL.Enable(EnableCap.DepthTest); 424 | if((Mode & DrawMode.Facet) != 0) { 425 | GL.BindVertexArray(Model.FacetData.GLAttrib); 426 | GL.Uniform4(Uniform("mode"), (int)ObjectType.ModelFacet, 0, 0, 0); 427 | GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Fill); 428 | GL.DrawArrays(BeginMode.Triangles, 0, Model.FacetData.Count); 429 | } 430 | 431 | GL.Disable(EnableCap.DepthTest); 432 | if((Mode & DrawMode.Edge) != 0) { 433 | GL.BindVertexArray(Model.EdgeData.GLAttrib); 434 | GL.Uniform4(Uniform("mode"), (int)ObjectType.ModelEdge, 0, 0, 0); 435 | GL.LineWidth(1); 436 | GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Line); 437 | GL.DrawArrays(BeginMode.Lines, 0, Model.EdgeData.Count); 438 | } 439 | 440 | if((Mode & DrawMode.Vertex) != 0) { 441 | GL.BindVertexArray(Model.VertexData.GLAttrib); 442 | GL.Uniform4(Uniform("mode"), (int)ObjectType.ModelVertex, 0, 0, 0); 443 | GL.PointSize(3); 444 | GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Point); 445 | GL.DrawArrays(BeginMode.Points, 0, Model.VertexData.Count); 446 | } 447 | } 448 | } 449 | 450 | public class ShadedRenderer : RendererWithCamera { 451 | string vertex_shader_source = @" 452 | #version 330 core 453 | 454 | uniform mat4 vp; 455 | 456 | layout(location = 0) in vec4 attr_position; 457 | layout(location = 1) in vec3 attr_normal; 458 | 459 | out vec3 normal; 460 | out vec4 position; 461 | 462 | void main() 463 | { 464 | position = attr_position; 465 | normal = attr_normal; 466 | vec4 p = vec4(position.xyz, 1); 467 | gl_Position = vp * p; 468 | }"; 469 | 470 | string fragment_shader_source = @" 471 | #version 330 core 472 | 473 | uniform vec3 light; 474 | uniform mat4 vp; 475 | 476 | in vec3 normal; 477 | in vec4 position; 478 | out vec4 outColor; 479 | 480 | void main() 481 | { 482 | float c = dot(normalize(light - position.xyz), normal); 483 | if(c < 0) c = -c; 484 | c = clamp(c, 0.1, 1); 485 | outColor = vec4(c, c, c, 1); 486 | }"; 487 | 488 | public MeshGraph Model; 489 | 490 | public ShadedRenderer() 491 | { 492 | SetSource(vertex_shader_source, "", fragment_shader_source); 493 | } 494 | 495 | public override void Render(RenderTarget rt) { 496 | rt.Use(); 497 | GL.UseProgram(Program); 498 | UpdateCamera("vp"); 499 | 500 | //GL.Enable(EnableCap.CullFace); 501 | GL.Disable(EnableCap.CullFace); 502 | GL.Enable(EnableCap.DepthTest); 503 | Vector3 v = Camera.Tf.Matrix.ExtractTranslation(); 504 | GL.Uniform3(Uniform("light"), ref v); 505 | GL.BindVertexArray(Model.FacetDataWithNormal.GLAttrib); 506 | GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Fill); 507 | GL.DrawArrays(BeginMode.Triangles, 0, Model.FacetDataWithNormal.Count); 508 | } 509 | } 510 | 511 | public class ObjectMapRenderer : RendererWithCamera { 512 | string vertex_shader_source = @" 513 | #version 330 core 514 | 515 | uniform mat4 vp; 516 | 517 | layout(location = 0) in vec4 position; 518 | 519 | out float index; 520 | out vec4 pos; 521 | 522 | void main() 523 | { 524 | pos = vec4(position.xyz, 1); 525 | gl_Position = vp * pos; 526 | index = position.w; 527 | }"; 528 | 529 | string fragment_shader_source = @" 530 | #version 330 core 531 | 532 | uniform ivec4 mode; 533 | 534 | in float index; 535 | in vec4 pos; 536 | out vec4 outColor; 537 | 538 | void main() 539 | { 540 | uint idx = uint(round(index) + 1); 541 | float val = uintBitsToFloat(uint(mode.x << 28) | idx); 542 | outColor = vec4(val, pos.x, pos.y, pos.z); 543 | }"; 544 | 545 | public MeshGraph Model; 546 | 547 | DrawMode Mode = DrawMode.All; 548 | 549 | public ObjectMapRenderer() 550 | { 551 | SetSource(vertex_shader_source, "", fragment_shader_source); 552 | } 553 | 554 | public override void Render(RenderTarget rt) { 555 | rt.Use(); 556 | GL.UseProgram(Program); 557 | UpdateCamera("vp"); 558 | 559 | GL.Disable(EnableCap.CullFace); 560 | 561 | GL.Enable(EnableCap.DepthTest); 562 | if((Mode & DrawMode.Facet) != 0) { 563 | GL.BindVertexArray(Model.FacetData.GLAttrib); 564 | GL.Uniform4(Uniform("mode"), (int)ObjectType.ModelFacet, 0, 0, 0); 565 | GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Fill); 566 | GL.DrawArrays(BeginMode.Triangles, 0, Model.FacetData.Count); 567 | } 568 | 569 | GL.Disable(EnableCap.DepthTest); 570 | if((Mode & DrawMode.Edge) != 0) { 571 | GL.BindVertexArray(Model.EdgeData.GLAttrib); 572 | GL.Uniform4(Uniform("mode"), (int)ObjectType.ModelEdge, 0, 0, 0); 573 | GL.LineWidth(1); 574 | GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Line); 575 | GL.DrawArrays(BeginMode.Lines, 0, Model.EdgeData.Count); 576 | } 577 | 578 | if((Mode & DrawMode.Vertex) != 0) { 579 | GL.BindVertexArray(Model.VertexData.GLAttrib); 580 | GL.Uniform4(Uniform("mode"), (int)ObjectType.ModelVertex, 0, 0, 0); 581 | GL.PointSize(3); 582 | GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Point); 583 | GL.DrawArrays(BeginMode.Points, 0, Model.VertexData.Count); 584 | } 585 | } 586 | } 587 | 588 | public class GridRenderer : RendererWithCamera { 589 | MeshGraph grid; 590 | 591 | string vertex_shader_source = @" 592 | #version 330 core 593 | 594 | uniform mat4 vp; 595 | 596 | layout(location = 0) in vec4 position; 597 | 598 | void main() 599 | { 600 | vec4 p = vec4(position.xyz, 1); 601 | gl_Position = vp * p; 602 | }"; 603 | 604 | string fragment_shader_source = @" 605 | #version 330 core 606 | 607 | out vec4 outColor; 608 | 609 | void main() 610 | { 611 | outColor = vec4(0.3, 0.3, 0.3, 1); 612 | }"; 613 | 614 | public GridRenderer() { 615 | SetSource(vertex_shader_source, "", fragment_shader_source); 616 | 617 | grid = new MeshGraph(); 618 | for(int i = -50; i < 50; i++) { 619 | MeshVertex v1 = grid.AddVertex(i, 0, -50); 620 | MeshVertex v2 = grid.AddVertex(i, 0, 50); 621 | grid.AddEdge(v1, v2); 622 | } 623 | 624 | for(int i = -50; i < 50; i++) { 625 | MeshVertex v1 = grid.AddVertex(-50, 0, i); 626 | MeshVertex v2 = grid.AddVertex(50, 0, i); 627 | grid.AddEdge(v1, v2); 628 | } 629 | 630 | grid.EdgeData.UpdateData(); 631 | } 632 | 633 | public override void Render(RenderTarget rt) { 634 | rt.Use(); 635 | GL.UseProgram(Program); 636 | UpdateCamera("vp"); 637 | 638 | GL.Enable(EnableCap.DepthTest); 639 | GL.BindVertexArray(grid.EdgeData.GLAttrib); 640 | GL.LineWidth(1); 641 | GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Line); 642 | GL.DrawArrays(BeginMode.Lines, 0, grid.EdgeData.Count); 643 | } 644 | } 645 | 646 | public abstract class TransformerRenderer : RendererWithCamera { 647 | string vertex_shader_source = @" 648 | #version 330 core 649 | 650 | uniform mat4 vp; 651 | uniform mat4 mMat; 652 | 653 | layout(location = 0) in vec4 position; 654 | 655 | out vec4 pos; 656 | 657 | void main() 658 | { 659 | pos = vec4(position.xyz, 1); 660 | gl_Position = vp * mMat * pos; 661 | }"; 662 | 663 | string fragment_shader_source = @" 664 | #version 330 core 665 | 666 | in vec4 pos; 667 | out vec4 outColor; 668 | uniform vec4 color; 669 | uniform ivec2 mode; 670 | 671 | void main() 672 | { 673 | if(mode.x == 1) 674 | outColor = color; 675 | else { 676 | uint idx = uint(dot(vec4(1, 2, 3, 0), color)); 677 | float val = uintBitsToFloat(uint(mode.y << 28) | idx); 678 | outColor = vec4(val, pos.x, pos.y, pos.z); 679 | } 680 | }"; 681 | 682 | protected MeshGraph axis; 683 | protected Vector4[] axisColors; 684 | 685 | public MeshGraph Model; 686 | public Vector3 Position { 687 | get { return Model.SelectedVerticesMidpoint; } 688 | } 689 | 690 | int type = 0; 691 | public bool ScreenMode = false; 692 | 693 | protected TransformerRenderer(ObjectType t) { 694 | SetSource(vertex_shader_source, "", fragment_shader_source); 695 | 696 | type = (int)t; 697 | axis = new MeshGraph(); 698 | 699 | axisColors = new Vector4[3]; 700 | axisColors[0] = new Vector4(1, 0, 0, 1); 701 | axisColors[1] = new Vector4(0, 1, 0, 1); 702 | axisColors[2] = new Vector4(0, 0, 1, 1); 703 | } 704 | 705 | public override void Render(RenderTarget rt) { 706 | rt.Use(); 707 | GL.UseProgram(Program); 708 | UpdateCamera("vp"); 709 | 710 | GL.Disable(EnableCap.DepthTest); 711 | GL.Disable(EnableCap.CullFace); 712 | 713 | for(int i = 0; i < 3; i++) { 714 | Matrix4 m = Tf(i); 715 | GL.UniformMatrix4(Uniform("mMat"), false, ref m); 716 | GL.Uniform4(Uniform("color"), ref axisColors[i]); 717 | GL.Uniform2(Uniform("mode"), ScreenMode ? 1 : 0, type); 718 | 719 | if(axis.Edges.Count > 0) { 720 | GL.BindVertexArray(axis.EdgeData.GLAttrib); 721 | GL.LineWidth(1); 722 | GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Line); 723 | GL.DrawArrays(BeginMode.Lines, 0, axis.EdgeData.Count); 724 | } 725 | 726 | if(axis.FacetData.Count > 0) { 727 | GL.BindVertexArray(axis.FacetData.GLAttrib); 728 | GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Fill); 729 | GL.DrawArrays(BeginMode.Triangles, 0, axis.FacetData.Count); 730 | } 731 | } 732 | } 733 | 734 | protected abstract Matrix4 Tf(int i); 735 | } 736 | 737 | public class TranslationTransformerRenderer : TransformerRenderer { 738 | protected Matrix4[] axisTfs; 739 | 740 | public TranslationTransformerRenderer() : 741 | base(ObjectType.TranslationTransformer) { 742 | new ArrowFactory().AddMeshGraphUpon(ref axis, false); 743 | axis.EdgeData.UpdateData(); 744 | axis.FacetData.UpdateData(); 745 | 746 | axisTfs = new Matrix4[3]; 747 | axisTfs[0] = Matrix4.CreateRotationY((float)Math.PI / 2); 748 | axisTfs[1] = Matrix4.CreateRotationX(-(float)Math.PI / 2); 749 | axisTfs[2] = Matrix4.Identity; 750 | } 751 | 752 | protected override Matrix4 Tf(int i) { 753 | return axisTfs[i] * Matrix4.CreateTranslation(Position); 754 | } 755 | 756 | public Vector2 ScreenVector(int axis) { 757 | Vector3 pos = Position; 758 | Vector4 v_org = Vector4.Transform( 759 | new Vector4(pos, 1), Camera.VPMatrix); 760 | v_org /= v_org.W; 761 | v_org.Y = -v_org.Y; 762 | 763 | pos[axis] = pos[axis] + 1; 764 | Vector4 v_end = Vector4.Transform( 765 | new Vector4(pos, 1), Camera.VPMatrix); 766 | v_end /= v_end.W; 767 | v_end.Y = -v_end.Y; 768 | 769 | return (v_end - v_org).Normalized().Xy; 770 | } 771 | } 772 | 773 | public class ScalingTransformerRenderer : TransformerRenderer { 774 | public Vector3 Scaling; 775 | protected Matrix4[] axisTfs; 776 | 777 | public ScalingTransformerRenderer() : 778 | base(ObjectType.ScalingTransformer) { 779 | new BoxMeshFactory(0.1f, 0.1f, 0.1f) 780 | .AddMeshGraphUpon(ref axis, false); 781 | foreach(MeshVertex v in axis.Vertices) 782 | v.Position = v.Position + new Vector3(0, 0, 3); 783 | 784 | MeshVertex vorg = axis.AddVertex(0, 0, 0); 785 | MeshVertex vhead = axis.AddVertex(0, 0, 3); 786 | axis.AddEdge(vorg, vhead); 787 | axis.EdgeData.UpdateData(); 788 | axis.FacetData.UpdateData(); 789 | 790 | axisTfs = new Matrix4[3]; 791 | axisTfs[0] = Matrix4.CreateRotationY((float)Math.PI / 2); 792 | axisTfs[1] = Matrix4.CreateRotationX(-(float)Math.PI / 2); 793 | axisTfs[2] = Matrix4.Identity; 794 | 795 | Scaling = new Vector3(1, 1, 1); 796 | } 797 | 798 | protected override Matrix4 Tf(int i) { 799 | Vector3 s = new Vector3(1, 1, 1); 800 | s[i] = Scaling[i]; 801 | return Matrix4.CreateScale(s) * axisTfs[i] * 802 | Matrix4.CreateTranslation(Position); 803 | } 804 | 805 | public Vector2 ScreenVector(int axis) { 806 | Vector3 pos = Position; 807 | Vector4 v_org = Vector4.Transform( 808 | new Vector4(pos, 1), Camera.VPMatrix); 809 | v_org /= v_org.W; 810 | v_org.Y = -v_org.Y; 811 | 812 | pos[axis] = pos[axis] + 1; 813 | Vector4 v_end = Vector4.Transform( 814 | new Vector4(pos, 1), Camera.VPMatrix); 815 | v_end /= v_end.W; 816 | v_end.Y = -v_end.Y; 817 | 818 | return (v_end - v_org).Normalized().Xy; 819 | } 820 | } 821 | 822 | public class RotationTransformerRenderer : TransformerRenderer { 823 | protected Matrix4[] axisTfs; 824 | 825 | public RotationTransformerRenderer() : 826 | base(ObjectType.RotationTransformer) { 827 | MeshVertex[] vs = new MeshVertex[32]; 828 | for(int i = 0; i < 32; i++) { 829 | double a = 2.0 * Math.PI * i / 32.0; 830 | vs[i] = axis.AddVertex(1.5f * (float)Math.Cos(a), 0, 1.5f * (float)Math.Sin(a)); 831 | if(i > 0) axis.AddEdge(vs[i-1], vs[i]); 832 | } 833 | axis.AddEdge(vs[31], vs[0]); 834 | axis.EdgeData.UpdateData(); 835 | 836 | axisTfs = new Matrix4[3]; 837 | axisTfs[0] = Matrix4.CreateRotationZ((float)Math.PI / 2); // x 838 | axisTfs[1] = Matrix4.Identity; // y 839 | axisTfs[2] = Matrix4.CreateRotationX((float)Math.PI / 2); // z 840 | } 841 | 842 | protected override Matrix4 Tf(int i) { 843 | return axisTfs[i] * Matrix4.CreateTranslation(Position); 844 | } 845 | 846 | public Vector2 ScreenVector(int axis, Vector3 startpt) { 847 | Vector3 pos = Position; 848 | Vector3 diff = startpt - pos; 849 | double a = diff[(axis + 1) % 3], b = diff[(axis + 2) % 3]; 850 | double r = Math.Sqrt(a * a + b * b); 851 | a /= r; b /= r; 852 | double angle = Math.Atan2(b, a) + Math.PI / 4; 853 | double a_ = Math.Cos(angle), b_ = Math.Sin(angle); 854 | 855 | a *= 1.5; b *= 1.5; 856 | a_ *= 1.414213562 * 1.5; b_ *= 1.414213562 * 1.5; 857 | 858 | diff[axis] = 0; 859 | diff[(axis + 1) % 3] = (float)a; 860 | diff[(axis + 2) % 3] = (float)b; 861 | 862 | Console.WriteLine(diff); 863 | 864 | Vector4 v_org = Vector4.Transform( 865 | new Vector4(diff + pos, 1), Camera.VPMatrix); 866 | v_org /= v_org.W; 867 | v_org.Y = -v_org.Y; 868 | 869 | diff[axis] = 0; 870 | diff[(axis + 1) % 3] = (float)a_; 871 | diff[(axis + 2) % 3] = (float)b_; 872 | 873 | Console.WriteLine(diff); 874 | 875 | pos[axis] = pos[axis] + 1; 876 | Vector4 v_end = Vector4.Transform( 877 | new Vector4(diff + pos, 1), Camera.VPMatrix); 878 | v_end /= v_end.W; 879 | v_end.Y = -v_end.Y; 880 | 881 | return (v_end - v_org).Normalized().Xy; 882 | } 883 | } 884 | 885 | public class SelectionBoxRenderer : RendererBase { 886 | MeshGraph plane; 887 | MeshGraph line; 888 | 889 | public Vector2 StartPoint; 890 | public Vector2 EndPoint; 891 | 892 | string vertex_shader_source = @" 893 | #version 330 core 894 | 895 | uniform vec4 rect; 896 | 897 | layout(location = 0) in vec4 position; 898 | 899 | void main() 900 | { 901 | gl_Position = vec4(rect.zw * position.xy + rect.xy, 0, 1); 902 | }"; 903 | 904 | string fragment_shader_source = @" 905 | #version 330 core 906 | 907 | out vec4 outColor; 908 | 909 | void main() 910 | { 911 | outColor = vec4(1, 1, 1, 0.2); 912 | }"; 913 | 914 | public bool RenderPlane = true; 915 | 916 | public SelectionBoxRenderer() { 917 | SetSource(vertex_shader_source, "", fragment_shader_source); 918 | 919 | plane = new MeshGraph(); 920 | var v1 = plane.AddVertex(0, 0, 0); 921 | var v2 = plane.AddVertex(0, 1, 0); 922 | var v3 = plane.AddVertex(1, 1, 0); 923 | var v4 = plane.AddVertex(1, 0, 0); 924 | 925 | plane.AddFacet(v1, v2, v3, v4); 926 | plane.FacetData.UpdateData(); 927 | 928 | line = new MeshGraph(); 929 | var l1 = line.AddVertex(0, 0, 0); 930 | var l2 = line.AddVertex(1, 1, 0); 931 | line.AddEdge(l1, l2); 932 | line.EdgeData.UpdateData(); 933 | } 934 | 935 | public override void Render(RenderTarget rt) { 936 | rt.Use(); 937 | GL.UseProgram(Program); 938 | 939 | Vector2 sp = StartPoint * 2 - new Vector2(1, 1); 940 | sp.Y = -sp.Y; 941 | Vector2 ep = EndPoint * 2 - new Vector2(1, 1); 942 | ep.Y = -ep.Y; 943 | Vector2 sz = ep - sp; 944 | 945 | GL.Disable(EnableCap.CullFace); 946 | GL.Disable(EnableCap.DepthTest); 947 | GL.Enable(EnableCap.Blend); 948 | GL.BlendFunc(BlendingFactorSrc.SrcAlpha, 949 | BlendingFactorDest.OneMinusSrcAlpha); 950 | 951 | if(RenderPlane) { 952 | GL.BindVertexArray(plane.FacetData.GLAttrib); 953 | GL.Uniform4(Uniform("rect"), sp.X, sp.Y, sz.X, sz.Y); 954 | GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Fill); 955 | GL.DrawArrays(BeginMode.Triangles, 0, plane.FacetData.Count); 956 | } else { 957 | GL.BindVertexArray(line.EdgeData.GLAttrib); 958 | GL.Uniform4(Uniform("rect"), sp.X, sp.Y, sz.X, sz.Y); 959 | GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Line); 960 | GL.DrawArrays(BeginMode.Lines, 0, line.EdgeData.Count); 961 | } 962 | 963 | GL.Disable(EnableCap.Blend); 964 | } 965 | } 966 | } 967 | -------------------------------------------------------------------------------- /CubeSharp/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /CubeSharp/tests/MeshGraphTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using NUnit.Framework; 4 | 5 | namespace CubeSharp { 6 | 7 | [TestFixture] 8 | public class MeshGraphTests { 9 | [Test] 10 | public void CreateTriangle() { 11 | MeshVertex v1 = new MeshVertex(1, 0, 1); 12 | MeshVertex v2 = new MeshVertex(2, 1, 0); 13 | MeshVertex v3 = new MeshVertex(1, 3, 1); 14 | 15 | MeshGraph mg = new MeshGraph(); 16 | mg.AddVertex(v1); 17 | mg.AddVertex(v2); 18 | mg.AddVertex(v3); 19 | 20 | MeshEdge e1 = mg.AddEdge(v1, v2); 21 | MeshEdge e2 = mg.AddEdge(v3, v2); 22 | MeshFacet f = mg.AddFacet(v1, v2, v3); 23 | 24 | CollectionAssert.Contains(mg.Vertices, v1); 25 | CollectionAssert.Contains(mg.Vertices, v2); 26 | CollectionAssert.Contains(mg.Vertices, v3); 27 | Assert.AreEqual(v1.EdgeConnecting(v2), v2.EdgeConnecting(v1), "", e1); 28 | Assert.AreEqual(v3.EdgeConnecting(v2), v2.EdgeConnecting(v3), "", e2); 29 | 30 | CollectionAssert.Contains(mg.Edges, v1.EdgeConnecting(v2)); 31 | CollectionAssert.Contains(mg.Edges, v2.EdgeConnecting(v3)); 32 | CollectionAssert.Contains(mg.Edges, v3.EdgeConnecting(v1)); 33 | CollectionAssert.Contains(e1.AdjacencyFacets, f); 34 | CollectionAssert.Contains(e1.Endpoints, v1); 35 | 36 | CollectionAssert.Contains(f.Vertices, v1); 37 | CollectionAssert.Contains(f.Vertices, v2); 38 | CollectionAssert.Contains(f.Vertices, v3); 39 | CollectionAssert.Contains(f.Edges, v1.EdgeConnecting(v2)); 40 | CollectionAssert.Contains(f.Edges, v2.EdgeConnecting(v3)); 41 | CollectionAssert.Contains(f.Edges, v3.EdgeConnecting(v1)); 42 | 43 | { // Remove the facet 44 | MeshGraph rm = mg.Clone(); 45 | MeshFacet rmf = rm.Eqv(f); 46 | rm.RemoveFacet(rmf); 47 | 48 | CollectionAssert.DoesNotContain(rm.Facets, rmf); 49 | CollectionAssert.DoesNotContain( 50 | rm.Eqv(e1).AdjacencyFacets, rmf); 51 | CollectionAssert.DoesNotContain( 52 | rm.Eqv(e2).AdjacencyFacets, rmf); 53 | } 54 | 55 | { // Remove an edge 56 | MeshGraph rm = mg.Clone(); 57 | MeshEdge rme = rm.Eqv(e1); 58 | MeshFacet rmf = rm.Eqv(f); 59 | rm.RemoveEdge(rme); 60 | 61 | CollectionAssert.DoesNotContain(rm.Edges, rme); 62 | CollectionAssert.DoesNotContain(rm.Facets, rmf); 63 | CollectionAssert.DoesNotContain( 64 | rme.AdjacencyFacets, rmf); 65 | CollectionAssert.DoesNotContain( 66 | rm.Eqv(e2).AdjacencyFacets, rmf); 67 | } 68 | 69 | { // Remove an vertex 70 | MeshGraph rm = mg.Clone(); 71 | MeshVertex rmv = rm.Eqv(v2); 72 | MeshEdge rme1 = rm.Eqv(v2.EdgeConnecting(rm.Eqv(v1))); 73 | MeshEdge rme2 = rm.Eqv(v2.EdgeConnecting(rm.Eqv(v3))); 74 | MeshFacet rmf = rm.Eqv(f); 75 | rm.RemoveVertex(rmv); 76 | 77 | CollectionAssert.DoesNotContain(rm.Vertices, rmv); 78 | CollectionAssert.DoesNotContain(rm.Edges, rme1); 79 | CollectionAssert.DoesNotContain(rm.Edges, rme2); 80 | CollectionAssert.DoesNotContain(rm.Facets, rmf); 81 | 82 | Assert.AreEqual(rmv.Edges.Count, 0); 83 | foreach(var v in rm.Vertices) 84 | Assert.AreEqual(v.Edges.Count, 1); 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CubeSharp 2 | 3 | Cubesharp is a 3D modelling software written in pure C# and OpenGL(OpenTK) [rid-Unity]. Quite a lot of common used functionalities in other modelling software has been integrated in Cubesharp to make it utility but a toy. 4 | 5 | ## Installation 6 | 7 | On Windows, well configure your Visual Studio and confirm its support for Nuget, which should reslove dependencies automatically. Just after an compilation and a run the installation completes. On Linux you can choose to build the project with `xbuild`, and you can find the executable in bin/Debug or bin/Release. Another option is MonoDevelop with CubeSharp.sln open in it. 8 | 9 | ## Basic Usage 10 | 11 | 1. Move the camera using middle mouse button(rotation), right button(translation) and mouse wheel(scaling). 12 | 2. Left button to select elements on the canvas. When an element turns red, it is being selected. 13 | 3. Select on the right pane to operate your model. Just try them all out! 14 | 15 | ### Transformers 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
Translating a cube facet brings about a longer cubeScaling a cube facet brings about a Tokyo Big SightRotating a cube facet brings about a Cayan Tower
29 | 30 | ### Split/Join 31 | 32 | Split a facet with an edge or split an edge with a vertex as you like. Left click for the position where you want to start breaking, and right click to end the process. 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 |
Before Joined (After Splitted)After Joined (Before Splitted)
44 | 45 | ### Connect/Delete 46 | 47 | Connection between vertices may produce edges or facets (based on the count). Selecting elements and clicking Connect button does this job. Deleting a vertices may also delete any adjacent facets and edges. Same thing happens to deletion on edges. 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 |
Before Connection (After Deleting Edges)After Connection (Before Deletion)
59 | 60 | ### Extrude 61 | 62 | Extruding gives you a chance to create a pop out (or erect) volume right out of the original facet. 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 |
Before Extrude(Splitted Cube)After Extrude
74 | 75 | ### Import/Export 76 | 77 | Currently only wavefront object is available, which is a widely supported format that can be used in many modeling engines and game engines. 78 | 79 | ### Selection 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 |
All are painted in red.Select all elements reachable.Select things by dragging a box
93 | 94 | ### Shading Mode 95 | 96 | Wireframe mode for precisely controling the model and shaded mode for art effects. 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 |
Wireframe Mode(Edit Mode)Shaded Mode(Preview Mode)
108 | 109 | ## Note on Development 110 | 111 | Here are some notes for code readers and maintainers. 112 | 113 | ### Renderer 114 | 115 | Each frame is consist of multiple render passes, and each render pass is controlled by a renderer. All of them are defined in `Renderer.cs`. There are renderers that are in charge of rendering the model itself, renderers that are in charge of rendering the transformers, and others are in charge of some miscellaneous UI elements. Some will not render to the screen visibly and render to video memory (render target). We will talk about it in the next section. 116 | 117 | ### Cast and Selection 118 | 119 | We calculate cast by fetch projection data from graphics card. An concept called ObjectMap exists in this project, representing which object is occupying which pixel. It is actually the decoded content in attached texture. Information is encoded in this way: 120 | 121 | Bits(Little Endian) | 28 | 4 | 32 | 32 | 32 122 | -----|---|----|----|----|---- 123 | Data | Object Index | Object Type | Position X | Y | Z 124 | 125 | When a selection is done, object map is fetched and decoded. When users simply clicked, an algorithm will find the element which has the most priority (including the type priority and the distance from click coordinate). When users box select, another algorithm will traverse pixels the box area covers and select everything. 126 | 127 | ### FuncPanel 128 | 129 | FuncPanel is the name of the right pane. All buttons and groups are generated by using reflection, so functionalities are very convenient to add or remove. 130 | 131 | ### MeshGraph 132 | 133 | This is core of this project. A MeshGraph is a plannar GRAPH in graph theory, as well as general triangle MESH data in graphics. It manages MeshVertex, MeshEdge and MeshFacet as its members, and process their adjacency relations. All mesh relavant algorithms are implemented in this class. We demand the `Meshxxxx` should have functionalities as follows: 134 | 135 | - MeshVertex: it should be able to list its adjacency edges and facets 136 | - MeshEdge: it should be able to list its adjacency facets and endpoints. To note that direction is very important and strict in graphics. 137 | - MeshFacet: it should be able to list its vertex sequence and edge sequence as well as calcuate its normal vector. 138 | 139 | -------------------------------------------------------------------------------- /docs/scrshot-box-select.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shihira/cube-sharp/631c48b2a74960b5cae3b42ba98d293a2f18b312/docs/scrshot-box-select.png -------------------------------------------------------------------------------- /docs/scrshot-connected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shihira/cube-sharp/631c48b2a74960b5cae3b42ba98d293a2f18b312/docs/scrshot-connected.png -------------------------------------------------------------------------------- /docs/scrshot-cs-shaded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shihira/cube-sharp/631c48b2a74960b5cae3b42ba98d293a2f18b312/docs/scrshot-cs-shaded.png -------------------------------------------------------------------------------- /docs/scrshot-cs-wireframe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shihira/cube-sharp/631c48b2a74960b5cae3b42ba98d293a2f18b312/docs/scrshot-cs-wireframe.png -------------------------------------------------------------------------------- /docs/scrshot-extrude.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shihira/cube-sharp/631c48b2a74960b5cae3b42ba98d293a2f18b312/docs/scrshot-extrude.png -------------------------------------------------------------------------------- /docs/scrshot-joined.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shihira/cube-sharp/631c48b2a74960b5cae3b42ba98d293a2f18b312/docs/scrshot-joined.png -------------------------------------------------------------------------------- /docs/scrshot-not-joined.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shihira/cube-sharp/631c48b2a74960b5cae3b42ba98d293a2f18b312/docs/scrshot-not-joined.png -------------------------------------------------------------------------------- /docs/scrshot-rotated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shihira/cube-sharp/631c48b2a74960b5cae3b42ba98d293a2f18b312/docs/scrshot-rotated.png -------------------------------------------------------------------------------- /docs/scrshot-scaled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shihira/cube-sharp/631c48b2a74960b5cae3b42ba98d293a2f18b312/docs/scrshot-scaled.png -------------------------------------------------------------------------------- /docs/scrshot-select-all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shihira/cube-sharp/631c48b2a74960b5cae3b42ba98d293a2f18b312/docs/scrshot-select-all.png -------------------------------------------------------------------------------- /docs/scrshot-select-neibour.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shihira/cube-sharp/631c48b2a74960b5cae3b42ba98d293a2f18b312/docs/scrshot-select-neibour.png -------------------------------------------------------------------------------- /docs/scrshot-splitted-cube.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shihira/cube-sharp/631c48b2a74960b5cae3b42ba98d293a2f18b312/docs/scrshot-splitted-cube.png -------------------------------------------------------------------------------- /docs/scrshot-translated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shihira/cube-sharp/631c48b2a74960b5cae3b42ba98d293a2f18b312/docs/scrshot-translated.png -------------------------------------------------------------------------------- /docs/scrshot-vertices.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shihira/cube-sharp/631c48b2a74960b5cae3b42ba98d293a2f18b312/docs/scrshot-vertices.png --------------------------------------------------------------------------------