development of a system to create smooth 3d …mdv/courses/cm30082/projects... · development of a...
TRANSCRIPT
DEVELOPMENT OF A SYSTEM TO
CREATE SMOOTH 3D COMPUTER
MODELS USING LEGO BUILDING
BLOCKS
Submitted by Michael Ballfor the degree of
BSc (Hons) Computer Science2006
DEVELOPMENT OF A SYSTEM TO CREATE SMOOTH 3DCOMPUTER MODELS USING LEGO BUILDING BLOCKS
Submitted by Michael Ball
COPYRIGHT
Attention is drawn to the fact that copyright of this dissertation rests with itsauthor. The Intellectual Property Rights of the products produced as part of theproject belong to the University of Bath(see http://www.bath.ac.uk/ordinances/#intelprop).
This copy of the dissertation has been supplied on condition that anyone who con-sults it is understood to recognise that its copyright rests with its author and that noquotation from the dissertation and no information derived from it may be publishedwithout the prior written consent of the author.
Declaration
This dissertation is submitted to the University of Bath in accordance with therequirements of the degree of Bachelor of Science in the Department of ComputerScience. No portion of the work in this dissertation has been submitted in supportof an application for any other degree or qualification of this or any other universityor institution of learning. Except where specifically acknowledged, it is the work ofthe author.
Signed...........................(Michael Ball)
This dissertation may be made available for consultation within the University Li-brary and may be photocopied or lent to other libraries for the purposes of consul-tation.
Signed...........................(Michael Ball)
ii
Abstract
There are many applications available to people creating 3-dimensional objects how-ever most are not easy for beginners to use, and depending on their complexity cantake many months to fully understand. In their most simple form these applica-tions provide the user with basic objects such as a cube, sphere, pyramid, and conewhich the user manipulates to create their desired object. A different method is toprovide the user with a ‘real world’ approach to building objects, such as that ofusing LEGO1 style bricks. Most people will at sometime have come into contactwith LEGO, or other similar building blocks, and they can relate this knowledge toa computer application providing a simulation of LEGO construction. LEGO canonly provide a ‘blocky’ representation of the modelled object, but smoothing couldbe applied to this LEGO structure to create a better representation of the modelledobject and to remove the ‘blocky’ appearance. This project looks into methods ofsmoothing LEGO structures, it examines the processes required to prepare a LEGOstructure for smoothing as well as the different smoothing methods that could beapplied.
1LEGO is a trademark of the LEGO Group
iii
Acknowledgements
Thank you to the late James Jessiman who in 1995 developed the LDraw file for-mat making the simulation of LEGO possible, and for projects such as this to beconsidered, and to my Supervisor, Prof Phil Willis.
iv
Contents
1 Introduction 1
2 Literature Review 32.1 Identification of the Project Areas . . . . . . . . . . . . . . . . . . . 32.2 Computer Simulation of LEGO . . . . . . . . . . . . . . . . . . . . . 32.3 The LDraw File Format . . . . . . . . . . . . . . . . . . . . . . . . . 42.4 The DirectX File Format . . . . . . . . . . . . . . . . . . . . . . . . 42.5 Smoothing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
2.5.1 Stud Removal . . . . . . . . . . . . . . . . . . . . . . . . . . . 52.5.2 Smoothing . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.6 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
3 Analysis 83.1 Loading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83.2 Internal Representation . . . . . . . . . . . . . . . . . . . . . . . . . 83.3 Rendering . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83.4 Conversion To Mesh . . . . . . . . . . . . . . . . . . . . . . . . . . . 93.5 Smoothing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93.6 Output to the DirectX File Format . . . . . . . . . . . . . . . . . . . 9
4 Requirements Specification 104.1 Functional Requirements . . . . . . . . . . . . . . . . . . . . . . . . . 104.2 Non-Functional Requirements . . . . . . . . . . . . . . . . . . . . . . 11
5 Design and Implementation 125.1 Design . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
5.1.1 Language Choice . . . . . . . . . . . . . . . . . . . . . . . . . 125.1.2 User Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . 125.1.3 Internal Representation . . . . . . . . . . . . . . . . . . . . . 135.1.4 Program Flow Diagram . . . . . . . . . . . . . . . . . . . . . 14
5.2 Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145.2.1 Parsing LDraw Files . . . . . . . . . . . . . . . . . . . . . . . 155.2.2 Applying the Transformation Matrices . . . . . . . . . . . . . 155.2.3 Rendering the LEGO Object . . . . . . . . . . . . . . . . . . 165.2.4 Converting the LEGO Object to a Mesh . . . . . . . . . . . . 175.2.5 Smoothing . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185.2.6 DirectX File Output . . . . . . . . . . . . . . . . . . . . . . . 25
6 Testing 266.1 Testing of Requirements . . . . . . . . . . . . . . . . . . . . . . . . . 26
v
6.1.1 Functional Requirements . . . . . . . . . . . . . . . . . . . . 266.1.2 Non-Functional Requirements . . . . . . . . . . . . . . . . . . 28
6.2 Testing the Smoothing Methods . . . . . . . . . . . . . . . . . . . . . 286.2.1 Definition Of Smooth . . . . . . . . . . . . . . . . . . . . . . 286.2.2 Laplacian Smoothing . . . . . . . . . . . . . . . . . . . . . . . 306.2.3 Taubin’s Surface Fairing . . . . . . . . . . . . . . . . . . . . . 306.2.4 Loop’s Masking . . . . . . . . . . . . . . . . . . . . . . . . . . 30
6.3 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
7 Critical Evaluation 347.1 File Parser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 347.2 The Mesh . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 347.3 Smoothing Extensibility . . . . . . . . . . . . . . . . . . . . . . . . . 347.4 Smoothing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
8 Further Work 36
9 Conclusion 37
Bibliography 38
A File Formats 40A.1 LDraw File Format . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40A.2 DirectX File Format . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
B Part Structures 50
C Overlap Resolving Images 52
D Code Listing 54D.1 Common.vb . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54D.2 datReader.vb . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57D.3 Form1.vb . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60D.4 ListRenderer.vb . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63D.5 ModelDefs.vb . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66D.6 OpenGLViewer.vb . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69D.7 Renderer.vb . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71D.8 Smoother.vb . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72D.9 SurfaceFinder.vb . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77D.10 Transformer.vb . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
vi
List of Figures
2.1 Subdivision of Quadrilateral . . . . . . . . . . . . . . . . . . . . . . . 52.2 Neighbourhood Structure (adapted from [15]) . . . . . . . . . . . . . 6
5.1 Interface Design . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135.2 Flow Diagram of System . . . . . . . . . . . . . . . . . . . . . . . . . 145.3 Traversal of Internal Representation During . . . . . . . . . . . . . . 165.4 First Smoothing Output . . . . . . . . . . . . . . . . . . . . . . . . . 205.5 Example of Subdivison . . . . . . . . . . . . . . . . . . . . . . . . . . 215.6 Smoothing with Subdivison . . . . . . . . . . . . . . . . . . . . . . . 215.7 Example of the Pinching Effect . . . . . . . . . . . . . . . . . . . . . 225.8 Isolated Shapes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235.9 Smoothing Applied after Overlapping Quadrilaterals are Resolved . 25
6.1 Test Results for Requirement 4 . . . . . . . . . . . . . . . . . . . . . 276.2 LEGO Pyramid . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 296.3 Square Pyramid . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 296.4 Black Triangle Showing the Desired ‘Smooth’ Result . . . . . . . . . 296.5 Test 1: Laplacian Smoothing of the Pyramid LEGO Object . . . . . 306.6 Test 2: Taubin’s Surface Fairing of the Pyramid LEGO Object . . . 316.7 Test 3: Loop’s Masking of the Pyramid LEGO Object . . . . . . . . 316.8 Test 3: Loop’s Masking of the Pyramid LEGO Object - Top View . 326.9 Gaps Created in the Mesh During Smoothing . . . . . . . . . . . . . 32
7.1 Parts Rendered with the Wrong Vertex Ordering . . . . . . . . . . . 35
C.1 Overlap Resolving for External Vertices and Line Intersections . . . 52C.2 Overlap Resolving for External Vertices and Intersections . . . . . . 52C.3 Overlap Resolving for Internal Vertices . . . . . . . . . . . . . . . . . 53
vii
Chapter 1
Introduction
There exists many methods of constructing 3-Dimensional objects using computers.When very accurate and technical objects, models, or buildings are being designedCAD (Computer Aided Design) systems are used. They allow the user to inputvery accurate measurements, and provide them with a preview of the planned item.These systems are generally very complex to use, can provide too much detail, andtake months of training to learn. Modern computer games also require 3D objects,these differ from the very accurate models CAD systems create for they need to berelatively small in the amount of detail they hold so as to be renderable in real time.
This project focuses more on the area of low detailed objects which would mostoften be used in real time rendering applications, such as games. The most commonway to create these objects is to start with some basic objects, a cube, sphere, pyra-mid and cone, and to manipulate these objects by applying different transformationsand object comparisons so as to create the desired object. This method works well,but is not that intuitive to new users.
Developments in game creation programming languages have given amateur andhobbyist game writers the ability to create 3D games through simplified languagesor point and click style interfaces. However they do not provide a method of creatingobjects, so the user is left having to use the limited supplied selection of objects, orlearn how to use one of the object creation systems.
In this project a different approach to object construction is examined. Most peoplehave at sometime come into contact with LEGO or other similar building blocks,and the concept of connecting these bricks together to create objects is relativelysimple. A method of creating objects could therefore be through a LEGO stylesimulator. This brings about its own problems, while the construction would nowbe relativity simple, the constructed objects will show LEGO characteristics, mostobviously the exposed studs, but also the ‘blocky’ effect LEGO objects have. Thesecharacteristics would give the objects a very limited usefulness.
This project addresses ways of making these objects more useful, it looks at theremoval of the most obvious LEGO characteristic, the studs, it then goes on to ad-dress the ‘blocky’ look of the objects by examining different methods of smoothingthem. The project looks at how to take a LEGO object, defined in a certain struc-ture, and prepare them for being smoothed, it then goes on to address problems
1
found during these processes.
This project does not look at the actual construction of the LEGO models, asmany LEGO simulator systems already exist, some of which are examined in theLiterature review. Aswell as existing systems, existing file formats for the storageof LEGO objects are also examined.
The dissertation is organised as follows; The literature review which examines dif-ferent LEGO simulators, LEGO file formats, and methods of removing the LEGOcharacteristics from the model. The analysis section examines the research findings,and specifies the direction the project should follow. The requirements are specifiedfrom the analysis and are found in the requirements section. Then follows a detailedexplanation of the implementation, describing findings, and problems experienced,along with methods carried out to fix these problems. The testing demonstrateswhether the project has met its requirements, and looks at the results of differentsmoothing methods. The project is evaluated in the critical evaluation and the di-rections the project could be extended towards are explained in the further worksection. The conclusion draws the project to a close and specifies if the project wasa success at showing LEGO blocks as a feasible method for 3D object construction.
2
Chapter 2
Literature Review
2.1 Identification of the Project Areas
Here we outline how the literature review is structured. The project is ultimatelyabout smoothing LEGO objects, and this will take numerous steps. The first ofthese to be examined are the methods of loading a LEGO object into the projectsapplication, and then once smoothed how to store it so it can be used by otherapplications. The easiest method of getting the object in to the application will beto support a file format in which LEGO objects are already stored. Research intoexisting LEGO simulators is needed to identify the most common, or most suitablefile format available. Once smoothed the object will again need to be stored. It ispresumable a LEGO file format will not be suitable, as a smoothed object will notcontain the same type of characteristics as a LEGO object. There are many 3D fileformats available, however a choice is made to use DirectX as it is well supportedand not application specific. After these areas have been examined the literaturereview moves onto examine different methods of smoothing the LEGO object.
2.2 Computer Simulation of LEGO
While this project is not focused on the actual method of simulating LEGO, it willbe necessary to connect with simulators already in existence. The simplest methodwill be to support the same file format as another simulator. We therefore haveto examine different LEGO simulators and select a file format which is either mostsupported, or best documented, preferably both will be true.
A sensible place to start looking for LEGO simulators is LEGO themselves andthey have produced LEGO Digital Designer [2] a system created using Qube soft-ware1. The simulator has a game feel to it, and supports brick snapping allowingthe bricks to be connected by the studs as with real LEGO. It uses LEGO’s ownfile format (.lxf) which was previously used by LEGO CAD for the developmentof LEGO Mindstorm robotics. The .lxf file is a package of three files; a thumbnailpreview of the object, an XML file specifying which bricks are used along with thecamera and object orientation when it was saved, and a .MODEL binary file. TheXML file only identifies which bricks are being used and their orientation, the actualbrick data describing its shape are stored in binary files within the LEGO Digital
1Q Technology’s generic game and interactive 3D creation package. [3]
3
Designers installation folders. With the data being stored in binary files it will bedifficult to use this file format within the project, especially as there appears to beno publicly available documentation on the file format.
There are many other LEGO simulators which do not need to be individually listedhere as this project is not concerned with the actually simulators, but with the fileformat they support. All of the other simulators that were found support a fileformat created by the LDraw Organisation. [6]. The organisations website providesa list of some of these simulators [7].
2.3 The LDraw File Format
The LDraw organisation was started in 1995 [6], and provide a completely unofficial,community run free CAD system, along with an extensive database of official partsproduced by the LEGO company. It is the LDraw representation of the officialparts (bricks) which are used in most of the LEGO simulation packages. LDrawworks on a community system whereby members submit parts, and then LDraw’sadministrators validate them to check they comply to the LDraw file format, and area valid real LEGO brick. The file format used to store these parts is fully documented(see A.1). Each brick normally consists of multiple parts, and is referenced in ahierarchial structure, whereby one part file may reference another which referencesanother and so on. The referenced parts are not always complete bricks but morelikely a simple structure, such as a box. These are generally created at unit scale,i.e. the maximum length of a side is 1 unit, and are transformed to the desired scalewhen used.
2.4 The DirectX File Format
While this project is focused on loading a LEGO object and then smoothing it, themain aim is to prove that a method of constructing useable 3D objects via a buildingblock system is feasible. For this to be the case the smoothed object will need tobe compatible with other applications. The LEGO file format will no longer be ofuse as it can not represent a smooth object, and while being compatible with manyLEGO simulators it does not stretch into other less specialised applications. A moreuniversally compatible file format supported by many applications, at least thoserunning under a version of Microsoft Windows, is DirectX (.x). The file format iswell documented (see A.2) and is architecturally and contextually free [11]. Allowingit to be an excellent candidate for the output of the smoothed object. DirectX ismore than just a file format, and covers a large area of multimedia from 3D models,to video, music, and real time animation. However for this project we are onlyconcerned with its use as a 3D object file format.
2.5 Smoothing
This can be split into two areas. The removal of the studs, and the smoothing ofthe LEGO object as a whole.
4
Figure 2.1: Subdivision of QuadrilateralMethod of subdividing a quadrilateral into triangles. Firstly divides into two
triangles. These in turn are split into four triangles. Each of these can then besplit into four more triangles and so on for the desired number of subdivisions
2.5.1 Stud Removal
From the research into the LDraw file format (see A.1), stud removal has become areasonably trivial problem of disregarding part files containing the word ‘stud’.
2.5.2 Smoothing
A lot of research has been carried out in the past examining different smoothingmethods and approaches, each with their own merits. Most of these approacheshave then seen further research carried out on them with further approaches beingcreated. This project is not about examining smoothing techniques and then en-hancing them, but more about applying these techniques to the problem of ‘blocky’LEGO objects, and proving its possible to smooth them. From carrying out initialresearch into different smoothing techniques it appears smoothing is used for noisereduction or removal from 2D images, or 3D objects. For the purpose of this projectthe ‘blocky’ steps of a LEGO object are noise from the smooth shape which it isrepresenting.
The LEGO object loaded from the LDraw format will consist of a very simplis-tic quadrilateral mesh, and will only provided a limited number of data points forthe smoothing techniques which could cause problems. To increase the number ofdata points a method of subdivision will need to be applied to the mesh. Subdivisioncan be used to divide the mesh into quadrilaterals or triangles, the most commonlyused in meshes are triangles. The original quadrilateral can be split in half to createtwo triangles, this is a preliminary step. Each triangle can then be split into 4 newtriangles by cutting the edges of the original triangle in half and then connectingthem together as shown in Figure 2.1. This step can be carried out as many timesas required.
Once subdivided the mesh will be able to have smoothing effects applied to it. Oneof the simplest and most used methods of smoothing is Laplacian smoothing [13].Laplacian smoothing examines each vertex individually, and uses a vertex neighbour-hood to adjust the examined vertex. The neighbourhood consists of all the verticesthe examined vertex is connected to by edges, see Figure 2.2. The examined vertexis then moved to the mean average location of all vertices in the neighbourhood, asdescribed by the equation 2.1
5
Figure 2.2: Neighbourhood Structure (adapted from [15])
v′i =∑
j∈i∗(vi)n
(2.1)
Where vi is the vertex being repositioned, i* its neighbourhood, and n the numberof vertices within its neighbourhood.
Due to Laplacian smoothing being so simplistic many people have created optimisa-tions and improvements to this method. Taubin’s smoothing scheme [15] attempts tofix a problem Laplacian, and many other methods suffer, which is that of shrinkage.Due to the underlying Laplacian method being an averaging process the model willultimately be shrunk to its origin. Halstead, Kass, and DeRose [1] attempt to solvethis problem by calculating how much the mesh will shrink during a smoothing step,and to enlarge it by that amount before applying the smoothing. This does fix theshrinking problem, however leads to exaggerated curvatures in the mesh. Taubin’sapproach uses Guassian smoothing, which replaces the Laplacian method, in thatwhile the neighbourhood of vertices is still used, each vertex in the neighbourhoodhas a weighting applied to it depending on how many neighbours are in its neigh-bourhood, and/or its distance from each neighbour. The Guassian smoothing stillsuffers from the shrinking problem. Taubin attempts to counteract this problem byapplying the smoothing process twice. The first time with a positive scaler appliedgenerating the smoother, but shrunk mesh. The second time with a negative scalerapplied, this enlarges the mesh again, but is still applying smoothing. The positivescaler is known as λ, and the negative as µ, and they are related such that 0<λ<1and µ+λ<0. The equation as defined by Taubin for the new position of each vertexin the mesh is:
v′i = vi + {λ, µ}∑
j∈i∗wij(vj − vi) (2.2)
Where vi is the vertex being repositioned, i* its neighbourhood, and wij the weight-ing applied to each of the vertices, vj in that neighbourhood.
Both these methods have looked at smoothing the mesh once the subdivisions havebeen calculated. Loop’s masking applies smoothing after each subdivision iteration
6
[9]. The subdivision is carried out as already explained, but at the end of each iter-ation all the vertices are repositioned. The method is still based on the Laplacianmethod, but again with the use of weighting as with Taubin’s method, however thereis no attempt made to counteract the shrinking problem. Again a neighbourhood ofvertices is used, and the equation for positioning the vertices shown below, as takenfrom [10].
v′ =α(n)v +
∑j∈i∗ vi
α(n) + n(2.3)
whereα(n) =
n(1− β(n))β(n)
(2.4)
where
β(n) =54− (3 + 2 cos(2Π/n))2
32(2.5)
where vi is the vertex being repositioned, i* its neighbourhood, and n the numberof vertices in the neighbourhood. α and β are a weighting algorithm applied to themean average vertex found from the vertices in the neighbourhood.
There are other methods of smoothing which try to reduce the shrinking effectsuch as a method of smoothing the mesh towards the shape of a sphere, which isbased upon Gaussian functions [4]. One of the prerequisites of this technique is tohave an object which is already ‘sphere like’, the example given is a CT scan of ahuman head. With LEGO objects this prerequisite can not be guaranteed and thismethod would be unlikely to give satisfactory results.
2.6 Conclusion
From the research carried out it has become apparent as to how to connect withexisting LEGO simulators through the support of the LDraw file format. Using theformat has also provided a simple method of removing the stud characteristic fromthe objects. The removal of the ‘blocky’ shape is therefore the area this project willinvestigate in more detail. It will use the researched smoothing techniques as itsbasis. These methods have been designed to remove noise, and it is yet to be seenif the ‘blocky’ appearance of LEGO falls into this category, however this projecthopes to show that it does, and that either one of these methods or some adaptionof theses methods may be useable to smooth LEGO objects.
7
Chapter 3
Analysis
This project can be split into six different sections; loading, internal representation,rendering, conversion to mesh, smoothing, and finally output in the DirectX fileformat.
3.1 Loading
The system will need to be capable of loading LEGO objects created in existingsimulators as the system will not provide a method of generating objects itself. Fromthe research of exisiting simulators described in the literature review the LDraw fileformat is not only the most utilised format but also a well documented and plaintext format. The shapes are described in terms of quadrilaterals and triangles, eachof which can easily be joined to construct a mesh representation of the LEGO objectrequired for smoothing.
3.2 Internal Representation
The system will need to be capable of holding the loaded LEGO object in an internalstructure. This structure needs to be generic enough to support many different fea-tures. Firstly it needs to be quick to populate during the loading procedure. It mustsupport the hierarchal structure given by the LDraw file format, allowing for studparts to be easily removed. The stud parts are made up from many different parts,each made from other different parts. To fully remove a stud it must be identifiableat the top level, and then all parts linked from it can be removed or ignored whenrendering and converting to the mesh form. The internal representation must alsobe capable of supporting the mesh representation of the object, this is so only oneinternal representation, rendering procedure are required.
3.3 Rendering
Once loaded the system will need to render the LEGO object. This will be doneusing OpenGL. OpenGL supports both quadrilateral and triangle shapes, these aredefined by their vertices as they are in the LDraw file format. Some care willneed to be taken as OpenGL supports back face culling and uses vertex ordering todetermine the front and back of the defined shapes. For OpenGL the front of a shapeis viewable when the vertices are ordered counter-clockwise, the LDraw file format
8
originally did not support vertex ordering, but since the rise in 3D renders and theconcept of vertex ordering, meta-commands have been added to the file format tospecify whether the parts are defined in clockwise or counter-clockwise ordering. Forshapes defined as clockwise they will need to be re-ordered before rendering can takeplace. The LDraw format also supports inverting referenced parts and this conceptalso needs to be catered for in the rendering, and internal structure. The rendereris also required to render the mesh structure, this should not require a differentapproach if the internal structure can support both forms, and hide the differencefrom the renderer.
3.4 Conversion To Mesh
Before smoothing can be applied the LEGO object needs to be described in terms ofa mesh rather than individual shapes. The smoothing methods require a neighbour-hood of vertices for each vertex that is being adjusted and this structure can onlybe created once the object is defined as a single mesh. The easiest method will beto still use the quadrilateral and triangle shapes to define the mesh. (The methodsresearched only require the use of a triangular mesh, but different approaches mayrequire the use of quadrilateral meshes). The vertices where these shapes meet willneed to be the same object1 within the internal structure so when an adjustment ismade to each vertex it will adjust all shapes connecting at that vertex.
3.5 Smoothing
The system will need to apply certain smoothing algorithms to the mesh structurecreated from the LEGO object. This project is investigating different smoothingapproaches in its attempt to find a suitable method to removed the ‘blocky’ appear-ance of the object. To begin the three methods of smoothing researched will beapplied and their results examined, once these originals tests have been carried out,any problems which affect all the methods will be examined and solutions produced.Once these are dealt with and if none of the smoothing methods produce the desiredeffect then they will be examined and improvements discussed, if not implemented.The smoothing part of the system will need to be easily extendable so changed ornew smoothing methods can be added for testing and evaluation.
3.6 Output to the DirectX File Format
The system will need to output the smoothed object so other applications can makeuse of them. A very commonly supported file format in most 3D applications, andgaming languages, at least on Microsoft Windows, is the Microsoft DirectX fileformat. It is capable of storing triangles which makes it very suitable for storingthe mesh representation of the smoothed LEGO object as this will be defined by anarray of triangles.
1By ‘the same object’ it is meant the actual structure or object used to store the vertex shouldbe the same, and not just a copy that contains the same values
9
Chapter 4
Requirements Specification
Here the formal requirements of the project are drawn up, they are split into twosections functional and non-functional. As stated by Sommerville [14] functionalrequirements are statements of services the system should provided, along with howit should behave to particular situations and inputs. Non-functional requirementsare constraints on the services of functions offered by the system, such as timingconstraints.
4.1 Functional Requirements
1. The system must load LEGO objects stored in the LDraw file format.
2. The system must store the loaded model in an internal structure that is adapt-able enough to hold the LEGO data, and mesh structure.
3. The system must be able to convert from the LEGO format into a meshstructure.
4. The system must be capable of rendering the unchanged LEGO object, andthe smoothed mesh structure.
5. The system must allow the user to rotate and zoom the view so as to be ableto fully examine the rendered objects. Wireframe, and other similar optionswould be helpful.
6. The system must remove the stud characteristics from the LEGO object.
7. The system must demonstrate the effects of different smoothing methods.
8. The system must be able to apply different smoothing techniques to the orig-inal model without having to reload the model each time.
9. The system must be easily extendable to allow different smoothing methodsto be added.
10. The system must be able to save the smoothed mesh structure in the DirectXfile format so as to be useable by other applications.
10
4.2 Non-Functional Requirements
11. The system should be able to load models within an acceptable duration.
12. The system should provide an easy method of selecting the different smoothingoperations.
11
Chapter 5
Design and Implementation
5.1 Design
This section outlines the high level design of the system, describing the followingitems; why the language used to develop the system was chosen, details about theuser interface, the internal representation which shall be used for the LEGO andmesh structures, and finally a flow diagram describing the structure of the system.
5.1.1 Language Choice
The system will be written in VB.NET. This language has been chosen for thefollowing reasons; firstly the author has industry level knowledge and experienceof this language allowing the development of the system to be relatively quick asthere will be no time required in learning the langauge. This system requires a largeamount of ground work in the form of file parser, renderer and mesh creation beforethe project part can be implemented, and with the relative short time scale quickdevelopment is a necessity. Other reasons supporting the choice of language is thatit supports objects which will be useful for the internal representation (see 5.1.3),and through a wrapper OpenGL is easily available. VB.NET can only run what isknown as managed code, the OpenGL dynamic linked library is not managed codeand the wrapper is used to simply make the functionality in OpenGL.DLL availableto VB.NET. The wrapper used is called CSGL (C# Graphics Library) [8], C# andVB.NET are essentially the same language compiling to the same byte code, andthis wrapper can be used by both. Other languages such as C++ would probablybe capable of faster computing for the smoothing algorithms and is able to connectdirectly to the OpenGL.dll, along with providing objects and so could be seen as abetter candidate, but the speed of development as already mentioned is crucial andso the choice of using VB.NET is made.
5.1.2 User Interface
The system only requires a basic user interface. It needs to allow the user to rotateand zoom in on the rendered LEGO Object, this will be done through mouse inputs.It needs to allow the user to select the LDraw file to load, this will be done byeither dragging the file onto the render window, or selecting the file in a standardWindows file selection box. The interface should provide the user with the abilityto select certain options such as the smoothing method, and rendering options (e.g.wireframe , no studs, smoothed object). This project is focused on the smoothing
12
Figure 5.1: Interface Design
of the LEGO object rather than the usability of the system and so the this limiteduser interface will be sufficient. The options will be represented as either radiobuttons or check boxes depending which is appropriate. Unallowed combinationsshould be disabled preventing the user from selecting them. Figure 5.1 shows howthe interface is simplistic, and as this project is concerned with observing the effectof smoothing methods on the LEGO object, that the interface is dominated by therendering display window. The application will consist of just this one interface.The ‘Load Model’ button will bring up a standard Microsoft Windows file selectiondialog from which the LEGO object can be selected. The top options change howthe LEGO object will be displayed, while the bottom ones make changes to the wayit will be smoothed.
5.1.3 Internal Representation
The internal representation of the object needs to be versatile so as to hold theLEGO object in both the LEGO parts, and mesh format. The structure will bedesigned to hold a LEGO object, this will contain an array of part objects similarto the command types defined by the LDraw file format. The part type, whichreferences another LEGO part in the LDraw file format will be represented by apart object that holds a reference to another LEGO object, because each part canbe seen as a smaller LEGO object which when combined create the complete LEGOobject this method will work well, and allows the hierarchical structure defined inthe LDraw format to be kept in the internal representation. The part type alongwith that of the line, triangle, quadrilateral, and special line parts will all be objectsinherited from a base class. These will store information relevant to that particularpart such as colour, normals, vertices, transformation matrix. The actual structureof each object is shown in Appendix B. When storing the mesh representation it
13
will consist of just one LEGO object, and that will hold an array of just triangle orquadrilateral parts. As both the object and mesh structures are held in this internalstructure the renderer only needs to be capable of handling this one representation.
5.1.4 Program Flow Diagram
Figure 5.2 presents the reader with a high level plan of the system to help guidethem through the implementation section described below.
Figure 5.2: Flow Diagram of System
5.2 Implementation
This section describes the processes carried out in the creation of the system. Thesmoothing section describes some of the findings found from the smoothing meth-ods described in the Literature Review (see 2.5.2). It then goes onto discuss genericproblems found by all the smoothing methods, and how these problems could beresolved. The fixes are then attempted, and the results from them are discussed.This section does not provide detailed results from the smoothing methods, onlyenough information to give reasoning for attempting the fixes. The testing section(see Chapter 6) gives a more thorough discussion about the obtained results fromdifferent smoothing methods.
This section is ordered according to the logical flow through the system, and closelyfollows the path described by the program flow diagram (see Figure 5.2), passingeach of the sections output to the next sections input.
14
5.2.1 Parsing LDraw Files
The parser takes an LDraw formatted file and generates an object which holdsan array of different parts (e.g. line, triangle, etc). The LDraw file format hascommands specified one to a line, and the parser reads each line examines thecommand type and carries out the correct task. For the basic commands; line,triangle, quadrilateral, and special line, a new object of that type (see AppendixB) is created and the vertex and colour fields populated. If the file is specified ashave the vertices clockwise ordered, the opposite to that required by OpenGL, thenthe vertices are swapped as they are entered into the internal structure so as to besuitable for rendering. For the more complex command; part reference, the systemcreates a new part object, and populates the transformation matrix, and name field.It sets the stud field to true if the name contains the word ‘stud’. If the previouscommand was ‘0 BFC INVERTNEXT’ (the invert next part meta-command) theinverted field is set to true. The parser then recursively calls itself with the partfile specified, and the returned object, being a LEGO object, is then stored in thepart field. All loaded parts are also stored in a hashtable identified by their partname, and when the part command is seen the system first checks if there is a partwith that name which has previously been loaded. If so then the other fields arepopulated as before, but rather than recursively calling the parser the object fromthe hashtable is copied into the part field. This is a deep copy, copying the partplus all sub parts within it. Once the bottom of the main object file is reached theparser returns the complete LEGO object.
5.2.2 Applying the Transformation Matrices
This section takes the LEGO object produced during loading, propagates the inver-sions, colours, and transformation matrices down through the structre, and returnsthis updated object.
To simplify the loading process the transformation matrices are not applied to partobjects during this procedure. The transformation matrix applies to all the partswithin the referenced part, this can be triangles, lines, and quadrilaterals, and alsoanother referenced part, along with its own transformation matrix. As the systemtraverses the internal structure it adds the seen transformation matrices to a stack,when it finds a shape defined it applies all the transformations on that stack tothe shapes vertices. When triangle and quadrilateral shapes are transformed theirsurface normals are calculated and stored in the objects normal field, these normalsare used later during the rendering process to produce the specular lighting effects.
As mentioned in the file parsing section an inverted flag can be set on the partobject, if this is the case then during the traversal of the structure the inversionsare carried forward through the parts, re-ordering the vertices if required.
Along with these the real colour RGB values are also calculated. LDraw coloursare specified through integer codes (see A.1), some of which are not an actual colourbut a reference to the ‘current’ colour. The ‘current’ colour is initially gray, and thenbecomes the last actual colour seen. As an example a LEGO brick is made up ofmany referenced parts, some of these being its studs. The triangles and quadrilater-als describing the stud part are defined with the colour code 16, the ‘current’ colour.
15
The brick is given a colour at the top level, and the ‘current’ colour is set to it, whenthe stud part is reached during the traversal it is given the ‘current’ colour, whichis the desired colour of the brick. In the internal representation it is the real colourfields which holds this calculated colour. Figure 5.3 shows this traversal process.
Figure 5.3: Traversal of Internal Representation During
5.2.3 Rendering the LEGO Object
This section takes the updated LEGO object created by the previous section, andrenders the object to screen.
16
The rendering process as mentioned previously (see 5.1.1) is carried out using OpenGL.OpenGL allows lines, triangles, and quadrilaterals to be defined by their vertices,details of which are specified in the LDraw file format, and have been reproducedin the internal representation of the LEGO object. It was therefore an easy processto create these in OpenGL. The vertex ordering had also been handled both duringloading and the transformation sections allowing the vertices to be used in the orderstored in the internal representation.
The rendering is carried out in a different thread to the rest of the applicationand consists of a loop instructing OpenGL to render the model on each cycle. Orig-inally OpenGL was being supplied with all the object data on every cycle and thetraversal of the LEGO object on each cycle made for a low frames per second (FPS)rate. To attempt to increase the FPS the internal structure was traversed once tocreate two stacks, one of instructions the other of values. The instructions consist oftriangle, quadrilateral, line, and colour, the values being either vertices, or the RGBcolour, and each command knowing how many values it required. Now each cycleof the rendering loop only has to run through the instruction stack rather than re-cursing through the LEGO object structure, and FPS rates did increase. At a laterstage the author was made aware of an improvement to this method of rendering.OpenGL natively supports the instruction list method, and this list is created in thesame way as the rendering is carried out but by specifying the rendering is to thelist. Now on each cycle of the rendering loop OpenGL is instructed to render thelist, and the details of the shapes do not have to be constantly specified. The systemwas adapted so that the stacks were still created from the LEGO object, and theOpenGL instruction list was built from a single render of these stacks, the controlloop was then updated to instruct OpenGL to render its internal list.
Requirement 5 specifies that the user must be able to rotate and zoom in on therendered object. This is done by applying a global rotation or adjusting the positionof the camera respectively, these are handled by OpenGL, and do not change any ofthe values within the LEGO object held in the internal structure.
The renderer can be supplied with certain display options, these include; wire framemode and the hiding of studs. The wire frame mode informs the OpenGL rendererto apply back and front face culling, (compared to the default of just back faceculling), when this option is specified the OpenGL renderer displays just the outlineof shapes, i.e. the wire frame. When the option to hide the studs is selected the listrepresentations are recreated from the main internal representation of the LEGOobject. However this time when the structure is being traversed if it comes across areference to a stud part (identified by the stud flag being set to true) it is skippedover, meaning the shapes which define the stud are not added to the lists, and hencenot displayed.
5.2.4 Converting the LEGO Object to a Mesh
This section takes the hierarchical object produced by the transformation section,and flattens it to just one object with triangles and quadrilaterals. The quadrilat-erals are converted into two triangles producing a triangle only mesh. The sharedvertices of each triangle are then merged, and this flattened mesh representation ofthe object returned.
17
Requirement 8 specifies the system should be capable of undoing the smoothingand reverting to the original LEGO object. The subdivision is the first step awayfrom the original LEGO object, so a copy is made before the flattening is applied,and this copy can be swapped in when the user wishes to return to viewing thenon-smoothed object.
The conversion is carried out by creating a new LEGO object which only holdsthe shapes, not the references to parts that contain shapes. This is done by travers-ing the original structure and adding any reference to a triangle or quadrilateralto the new object. The new object now holds the flattened version of the LEGOobject. The desired mesh is one containing only triangles and so as mentioned anyquadrilaterals are divided in half creating two new triangles. These triangles areadded to the LEGO object, and the original quadrilateral is removed.
Now we have a triangular mesh however each triangle has its vertices defined asdifferent objects. This means two triangles which share the same vertex locationhave independent objects specifying it. If this was not fixed so both triangles de-fined the vertex with a shared object then the creation of the neighbourhoods usedduring smoothing would be difficult. Also every vertex object would have to beupdated when smoothing is performed whereas if the triangles share the vertex ob-ject, the vertex only needs adjusting once for all the triangles with that vertex to bealtered. This method is known as merging the vertices and is performed by loopingthrough every vertex of every shape in the flattened LEGO object. For each vertexa list is checked, if that list contains a vertex with the same values then the vertexobject is redirected to point to this one, if no vertices match it on the list, then it isadded. Once completed all vertices defining the triangles in the mesh are containedin the list, and it is this list of vertices which is used during the smoothing methods,as changes to these change the triangles in the flattened LEGO object structure.
5.2.5 Smoothing
This section takes the flattened mesh structure and applies smoothing methods. The‘smoothed’ mesh is then returned as a LEGO object which can be rendered usingthe same method as the orginial LEGO object.
It is stated in requirement 9 that the system must be written in such a way thatsmoothing methods can easily be added to the system. The system could be moresimple to extend by using a plug-in based module system, where each smoothingmethod is a new module. However due to time constraints a slightly less extend-able method has been adopted. The file Smoother.VB consists of multiple controlsubroutines. These control the subdivision and smoothing iterations, and applyingof the smoothing at the correct time, e.g. Loops masking is applied during eachsubdivision iteration whereas the others are applied after the subdivision has takenplace. These control subroutines call the relevant methods, such as the subdivider,and more importantly the correct smoothing algorithm. To allow the user to selectthe type of smoothing they wish to carry out the interface also has to be updatedto add the smoothing method as an option. This is by far not the simplest form ofextensibility, however by creating a control loop calling the smoothing techniques,and adding the smoothing method to the user interface, the new method can be
18
added to the system without having to make changes to the existing code.
As described in the literature review there are three smoothing techniques that willbe implemented first to show if the idea is plausible, and also to find any problemswhich are generic to them all and which can hopefully be eliminated.
Vertex Neighbourhood
The three smoothing techniques require a vertex neighbourhood. The neighbour-hood identifies, for each vertex within it, which other vertices it connects to. Thisinformation is used by each smoothing method to create the repositioned vertices,and ultimately the smoothed object. The literature review covers the details of thesesmoothing methods and how they utilise the neighbourhoods (see 2.5.2).
This section explains how the neighbourhood object is actually constructed by thesystem. The neighbourhood structure is represented by a hash table where the keyvalues are all the vertices from the mesh, and the data values are arrays of verticeswhich the key vertex connects to. This structure allows the individual vertices toquickly obtain their part of the neighbourhood when required during the smooth-ing processes. The structure is populated by iterating through each shape withinthe LEGO object representation of the mesh, and by each of these shapes iteratingthrough its vertices checking if they are already one of the keys in the neighbourhoodstructure. If so then the other vertices in the shape which connect to this vertexare added to the array in the hast table that is identified by this vertex. If a vertexis not already one of the keys in the hash table then an array is created containingthe vertices it connects to in its shape and this array is added to the hash tableusing the vertex as the key. Listing 5.1 is a pseudo example for constructing theneighbourhood structure from a triangular mesh.
19
Listing 5.1: Construction of Neighbourhoods.¨ ¥12 for each t as triangle in LEGO_Object3 for each v as vertex in t45 if neighbourhood.keys.contain(v) then6 ’Add the other vertices of the triangle to the
7 ’array identified by the vertex , v.
89 for each v2 as vertex in t where v2 is not v
10 neighbourhood(v).add(v2)11 next1213 else14 ’Key value not already identifying a neighbourhood
15 ’so add a new array containing this shapes vertices
16 ’and identified by v to the hash table.
1718 Dim arr as array19 for each v2 as vertex in t where v2 is not v20 arr.add(v2)21 next22 neighbourhood.add(v,arr)2324 end if2526 next27 next§ ¦
After the neighbourhood structure was populated the smoothing processes could beapplied using the formulas given in the literature review (see 2.5.2), and the resultsobserved, see Figure 5.4. As shown the ‘supposedly’ smoothed object is not verysmooth. This is partly due to the low density mesh, which is caused by not havingapplied any subdivision steps. Due to this the smoothing method only has a limitednumber of vertices to move and is unable to create curves.
Figure 5.4: First Smoothing OutputTaubin smoothing method applied 5 times, no subdivision.
20
Figure 5.5: Example of Subdivison
Figure 5.6: Smoothing with SubdivisonSubdivision applied 3 times, Taubin smoothing applied 5 times.
Subdivision
The literature review describes a method for subdivision and this method is used forall the smoothing methods to generate the higher density mesh. This is implementedby iterating through the list of triangles in the mesh and dividing them up to createfour new ones (see 2.5.2, and Figure 2.1). Figure 5.5 shows subdivision being appliedto the LEGO object. The smoothing methods can now be applied to the subdividedmesh and improvements are immediately seen, Figure 5.6 was subdivided three timesbefore having the Taubin smoothing method applied five times as before (see Figure5.4).
A system has now been built which can smooth LEGO objects to some degree.The testing section describes what the perfect ‘smoothed’ object would be, and howwell the different smoothing methods fair at reaching this goal. During the testing aproblem was discovered which applies to all the smoothing methods, and an attemptis made to correct it.
Surface Removal
The LEGO object to mesh representation is carried out by flattening the hierarchicalstructure to an array of shapes (see 3.4). The problem is this method does not take
21
Figure 5.7: Example of the Pinching EffectSigns of pinching at the location highlighted.
into consideration whether these shapes are overlapping each other. So for the ex-ample shown in Figure 5.7 the mesh is that of two complete bricks, and there is stilla connection between where the two arrows are indicating. This connection causesthe averaging effect applied by the smoothing methods to pull the points inwardscreating a pinching effect. To remove this problem these overlapping areas of shapeswould have to be removed so that the constructed mesh is of only the outer ‘visible’parts.
Most LEGO bricks are constructed from quadrilaterals and so to simplify the surfaceremoval process all but the quadrilateral shapes are disregarded from the LEGO ob-ject representation. The removal process can be split into two sections; removal ofthe overlaps, and removal of trapped shapes. The first of these have been mentionedalready, the trapped shapes can occur once the removal of overlaps has been carriedout. Figure 5.8 shows how shapes can become isolated from the exterior mesh. Itshows a cross section of two bricks, one on top of the other, and then shows howafter surface removal the highlighted blue surfaces are now isolated from the theouter mesh. This may not initially appear to be a problem as the isolated surfaceswill not be visible. However once smoothing is applied it could become a problem,firstly these extra shapes do not need to be smoothed so a waste of resources occurs,but a more important issue is that because these shapes are not connected to theexterior mesh they will not smooth in the same direction, and could move throughthe exterior mesh, or worse connect with it creating undesired effects.
The surface removal process has many steps to it, requiring both surface and isolatedshape removal. The surface removal has to be able to recognise the different waysin which two quadrilaterals can overlap and apply the correct methods to resolve it.The isolated shape removal is carried out in two steps; firstly find a quadrilateral onthe object which can see the outside, i.e. a line can be drawn from the quadrilateraldirectly to the LEGO object’s bounding box without colliding with another shape.Once a quadrilateral meeting this requirement has been found, all quadrilateralsthat share one of its vertices, and all the quadrilaterals that share one of thosequadrilaterals vertices, and so on are marked as external. After this process iscomplete any quadrilaterals that are not marked as external are therefore isolatedand can be removed.
22
Figure 5.8: Isolated ShapesInternal shapes can become isolated once surface removal is applied.
Below is the high level plan for overlapping and isolated shape removal, followed bya detailed plan for resolving the overlapping quadrilaterals.
1. Remove Overlapping Quadrilaterals
Resolve overlapping conflicts to leave only the parts of quadrilaterals whichare not covered by another quadrilateral.
2. Find an Outside Quadrilateral
Find a quadrilateral which can draw a line between itself and the objectsbounding box such that the line does not intersect with any other quadrilat-erals.
3. Find all Touching Quadrilaterals
Once one quadrilateral can be proven to be on the external mesh then allshapes it recursively connects to are also on the external mesh. Any that cannot be recursively connected to are isolated and can be removed.
Plan for Resolving Overlapping Quadrilaterals
This process is broken down so only two quadrilaterals are compared with eachother at any time. Three outcomes of the comparison can arise, either they are notoverlapping, requiring no change, one or both are completely covered by the other,requiring completely covered quadrilateral(s) to be removed from the mesh object,or one or both are partially covered, requiring removal of partially covered quadri-lateral(s) from the mesh object and the addition of the correct quadrilateral shapeto fill the area not covered (this may require more than one quadrilateral). Thesenewly added quadrilateral(s) are added to the end of the comparison list incase theystill overlap with another quadrilateral.
The first step is to analyse the vertices of each quadrilateral and determine if theyare either external (not inside other quadrilaterals), internal, intersection (is in thesame position as a vertex on the other quadrilateral), or line intersections (is on anedge of the other quadrilateral). Once the vertices have been correctly categorisedthe system is able to select which method to use on each quadrilateral to resolve theoverlaps.
23
• Both quadrilaterals have all external vertices and no intersections
Nothing needs to be done as neither of the quadrilaterals are overlapping
• Neither quadrilateral has internal vertices and either one or bothhave line intersections
As no internal vertices, then for each quadrilateral its external verticesare left connected as they were. The line interceptions are connected together,and then these connected parts are joined to form the final quadrilateral. Theoriginal quadrilaterals are removed and the new quadrilaterals added to themesh structure. (See C.1).
• Neither quadrilateral has internal vertices or line intersections, butthey have at least one interception
The quadrilaterals could just be touching, or if they both have the sameheight, one could be inside the other as shown by C.2. The system checks ifone quadrilateral is completely inside the other, if so it can be removed. Thequadrilateral that is not inside needs to have the area of overlap removed fromit. This is done by keeping the external vertices joined, the interceptions arethen connected together by examining where on the other shape they were,and which connections are valid. These connected parts are joined to form thefinal quadrilateral.
• One or both quadrilaterals have internal vertices
The two quadrilaterals overlap and one or both of them have at least onevertex inside the other quadrilateral. The exterior vertices (should there bemore than one) remain joined as they were. The internal vertices (should therebe more than one) are kept joined as they were when in the other quadrilateral.The exterior and internal vertices are then connected by the relevant lineinterceptions, see C.3. As can be seen shapes with more than four vertices canbe created. In this case these need to be divided to create quadrilaterals.
Due to time constraints not all of this section was implemented in the final system.The vertex categorising and overlap resolution is fully implemented except for thecase when internal vertices are present. For this the correct shape outline is obtained,but there is no splitting carried out on shapes with more than four vertices. Theexterior finding algorithm which works by proving one quadrilateral is on the outside,and then removes quadrilaterals which are not recursively connected to it is notimplemented at all. However the object used to display the pinching effect whichintroduced the need for this overlap removal function (see Figure 5.7) can be shownsmoothed after the overlaps have been removed. As figure 5.9 shows the smoothingnow shows no signs of the pinching effect.
24
Figure 5.9: Smoothing Applied after Overlapping Quadrilaterals are ResolvedThe pinching effect has been removed, and the object is able to smooth properly.
5.2.6 DirectX File Output
Due to time constraints this section was unable to be implemented. The aim of theproject was to show if LEGO objects could be smoothed so as to be useable in otherapplications. The rendered output of the smoothed LEGO objects can show if thisis possible and the DirectX output is only required to allow the smoothed model tobe used in other applications and not in proving that it was possible to create it.
25
Chapter 6
Testing
The testing is split into two sections; the first looks at how well the system meetsthe requirements specified in Chapter 4, the second looks at how well the smoothingmethods adopted, behave when applied to LEGO models.
6.1 Testing of Requirements
6.1.1 Functional Requirements
Requirement 1: The system must load LEGO objects stored in the LDrawfile formatThis requirement is met. All the commands, and required meta-commands, i.e.those concerned with vertex ordering, are fully parsed and understood. The internalrepresentation is populated with the data, and after the initial load the transforma-tions are applied, and the colours calculated.
Requirement 2: The system must store the loaded model in an inter-nal structure that is adaptable enough to hold the LEGO data, and meshstructureThis requirement is met. The internal structure consists of an array of parts. TheLEGO hierarchy is supported by the ‘part type’ being able to contain another LEGOobject. Support for the mesh structure is obtained by storing the mesh as individualtriangles, these triangle parts are stored in the array within the internal structure.
Requirement 3: The system must be able to convert from the LEGOformat into a mesh structureThis requirement is met. The system has a process which takes the hierarchicalLEGO object representation and essentially flattens it to create one array of parts,the line parts are removed, and the quadrilaterals split into triangles. While beinga mesh it does however still contain the internal sections of the structure and theremoval of this internal structure is not fully implemented, although it has beenplanned, and the method is described.
Requirement 4: The system must be capable of rendering the unchangedLEGO object, and the smoothed mesh structureThis requirement is met on both accounts. Figure 6.1 shows the LEGO objectas it was described in the LDraw file, and again after subdivision and Laplacian
26
smoothing has been applied.
Figure 6.1: Test Results for Requirement 4Shows the rendering of a LEGO object before and after the smoothing process
Requirement 5: The system must also allow the user to rotate and zoomthe view so as to be able to fully examine the rendered objects. Wire-frame, and other similar options would be helpful.This requirement is met. The rendered object can be rotated and zoomed in uponby using the mouse. The system supports showing the model in wireframe mode.It also allows the user to show the object with or without sides, and will show thenormals of each shape, useful when determining if a shape’s vertices are ordered inthe correct orientation. It will also show the user the location and type of the inter-sections used during the internal surface removal algorithm, useful for debugging.
Requirement 6: The system must remove the stud characteristics fromthe LEGO object This requirement is met. As shown in Figure 6.1 the studs areremoved from the LEGO object during smoothing.
Requirement 7: The system must demonstrate the effects of differentsmoothing methodsThis requirement is met. The system allows the user to apply three smoothingtechniques; Taubin Surface Fairing, Loop’s Masking, and Laplacian Smoothing, de-scriptions of which are found in the literature review 2.5.2.
Requirement 8: The system must be able to apply different smooth-ing techniques to the original model without having to reload the modeleach timeThis requirement is met. The system takes a copy of the non-smoothed object be-fore converting it to the mesh format and applying smoothing. When the smoothingmethod is changed, or the smoothing option turned off the smoothed model is dis-regarded and the non-smoothed copy is copied back in to be the current internalrepresentation of the object.
Requirement 9: The system must be easily extendable to allow differ-ent smoothing methods to be added easilyThis requirement is partially met. The system makes it reasonable easy to add a
27
new smoothing method, and while existing code does not need to be changed to adda smoothing method, the smoothing code would have to be added and the projectre-compiled.
Requirement 10: The system must be able to save the smoothed meshstructure in the DirectX file format so as to be useable by other applica-tionsThis requirement is not met. Due to time constraints this feature had to be removed.
6.1.2 Non-Functional Requirements
Requirement 11: The system should be able to load models within anacceptable durationThis requirement is met. While ‘acceptable duration’ is not an exact measurementthis requirement can be stated as being met because it is capable of loading largeLEGO objects almost instantaneously.
Requirement 12: The system should provide an easy method of selectingthe different smoothing operationsThis requirement is met. The user is provided with a list of radio buttons to selectthe type of smoothing that should be applied. They are also provided with theability to change the number of subdivision and smoothing iterations which will becarried out.
It can be seen that 10 out of the 12 requirements are fully met. Out of the tworequirements that are not; DirectX file output is not essential to understanding ifbuilding smoothed ‘useful’ objects from LEGO bricks is plausible. The smoothingis more essential, however new methods can be applied with only a limited amountof change to existing code being required. Neither of these two requirements arecritical to the project, and the results the system generates.
6.2 Testing the Smoothing Methods
This section investigates the three smoothing methods; Taubin, Loop, and Lapla-cian, and investigates the positive and negative features of these when applied toLEGO bricks. A definition of ‘smooth’ is suggested and the smoothing methods arecompared to determine which perform the best against this criteria.
6.2.1 Definition Of Smooth
For this project the idea of a smooth shape is one that removes the ‘blockly’ appear-ance. Due to the size of the blocks used in LEGO, smooth shapes such as pyramidscan only be approximately constructed, see Figure 6.2. A ‘smooth’ version of thisapproximation would be a square based pyramid, see Figure 6.3. This section willtest how well these smoothing methods perform when applied to LEGO objects toobtain this definition of ‘smooth’.
Smoothing can be greatly affected by the number of subdivisions and smoothingiterations applied, as is demonstrated by Figure 5.4. The testing attempts to applythe correct number of these iterations to produce the best smoothness so as to show
28
Figure 6.2: LEGO Pyramid
Figure 6.3: Square Pyramid
if that smoothing method is capable of producing the correct result.
The testing uses the LEGO pyramid shown in Figure 6.2. The result images showthe pyramid from a side on angle. A black outline defines where the smooth out-line should occur (see Figure 6.4), a red outline shows an outline of the size of thesmoothed result, this identifies any shrinkage, and also any undulations.
Figure 6.4: Black Triangle Showing the Desired ‘Smooth’ Result
29
As already mentioned (see 5.2.5) the system does not completely remove internalshapes from the mesh, and this does affect the smoothing process. This is describedin more detail in each test.
6.2.2 Laplacian Smoothing
As mentioned in the literature review this is one of the simplest smoothing methodsand has the known problem of shrinkage. As can be seen from Figure 6.5 the
Figure 6.5: Test 1: Laplacian Smoothing of the Pyramid LEGO Object4 subdivisions and 300 smoothing iterations were applied
red triangle shows quite a significant amount of shrinkage to the pyramid. Thesmoothed object does however fit quite closely to the reduced size of that triangle,and shows clearly that the pyramid has been smoothed to some degree. Signs of the‘pinching’ effect can be seen occurring between the different levels of the pyramid,and straighter edges could be obtainable if this problem was removed.
6.2.3 Taubin’s Surface Fairing
Taubin’s approach is based upon the laplacian smoothing approach, but tries toreduce the amount of shrinkage. As can be seen from Figure 6.6 there is only a verysmall amount of shrinkage. Unfortunately it is also clear that this method whiledoes smooth the sharp edges, it does not remove the ‘blocky’ look of the object.Again the ‘pinching’ effect can be seen especially on the right hand side between thegreen and red bricks.
6.2.4 Loop’s Masking
Loop’s Masking method is different in that it does not apply the smoothing afterthe subdivision process, but during it. As can be seen from Figure 6.7 there hasbeen only a small amount of shrinkage, but more clearly is the issue of the differentlayers splitting apart. From Figure 6.8 it can be seen that while the object lookssmooth, many holes have also appeared over the object.
30
Figure 6.6: Test 2: Taubin’s Surface Fairing of the Pyramid LEGO Object3 subdivisions and 300 smoothing iterations were applied
Figure 6.7: Test 3: Loop’s Masking of the Pyramid LEGO Object5 subdivisions and smoothing iterations were applied
The problem lies with the uneven density of the mesh, and not particulary withthis method. The other two smoothing methods have not suffered as badly fromthis problem, although small gaps can be seen in Figure 6.5 between the layers. Theproblem arises from the way the mesh structure is created. Each original shape inthe LEGO object is subdivided the same amount of times, this means that a shapewith a small area is subdivided the same amount of times as a shape with a largerarea, causing the small shape to have more triangles in the same area as that ofthe large shape. The different densities affect how the smoothing methods work, ashigh density meshes take more smoothing iterations to create the same amount ofsmoothness as low density meshes, therefore high density areas will shows less signsof smoothing than low density areas. Due to the uneven density of the mesh the
31
Figure 6.8: Test 3: Loop’s Masking of the Pyramid LEGO Object - Top View5 subdivisions and smoothing iterations were applied
smoothing is not performed evenly over the whole object.
Another problem the different densities cause, and which is the problem clearlyseen in Figure 6.7, is that of gaps occurring in the mesh. In LEGO objects mostof the shapes have different areas to those of their neighbours, and consequentlythe mesh has areas of different densities. Different density meshes do not connecttogether well and smoothing can causes these edges to be pulled apart causing thegaps. Figure 6.9 shows how the gaps are created, the diagram is simplified to onlysmooth the two vertices that cause the gap to appear. The red dots show whichvertices from the meshes of the two shapes connect. The two which do not connectcan then be seen to be smoothed away from the other shape causing the gaps toappear. The more times the smoothing methods are applied the gaps are expandeduntil the situation shown in Figures 6.7, and 6.8 occurs.
Figure 6.9: Gaps Created in the Mesh During Smoothing
32
6.3 Conclusion
It can be seen from the test images that Laplacian smoothing is most likely to bethe better method of smoothing LEGO objects, however it does suffer badly fromshrinkage. Taubin surface fairing does counteract the shrinking problem as it wasdesigned to, however it does not remove the ‘blocky’ look of the LEGO object, andso is not a very good candidate for this particular purpose. Loops method providesvery smoothed shapes, but unfortunately is greatly affected by the internal parts ofthe mesh, and the changes in mesh density, leaving it difficult to determine whether,with these two problems removed, this could be a viable smoothing method.
33
Chapter 7
Critical Evaluation
This section examines the system and discusses whether the approaches taken werethe most appropriate, and whether with the knowledge gained from developing thesystem anything would have be done differently.
7.1 File Parser
The LDraw file format is very well documented and the development of the file parserwas a relatively simple task, and while the system handles the vertex ordering formost of the basic shapes there are some more complex objects where the orderingappears to be the wrong way around. The reason for this was never found, the filespecification was adhered to, and the code examined, all seeming correct. It couldstill be a problem with the system, or there is a chance it could be a problem withthe actual model . Figure 7.1 demonstrates the problem, while there appears to begaps they are actually being back face culled by the OpenGL renderer.
7.2 The Mesh
The system has to convert the LEGO object into a mesh representation to be ableto provide a basis for the smoothing methods. The principle of how this process iscarried out is reasonably simple and effective, however it does create a mesh withvaried densities and internal parts. However these can both be fixed; the internalparts by the method detailed within the implementation, and the varied densitiesby creating fixed sized subdivisions and rather than the size of each triangle beingrelated to the initial area of the shape, the number of subdivision will be. Withboth these fixes applied then this method is a reasonable way of constructing themesh for applying the smoothing to.
7.3 Smoothing Extensibility
As discussed within the testing of the requirements the smoothing is theoreticallyquite easy as the system can allow for new methods to be added without needingto make changes to the existing methods. However the source code would needrecompiling and this stops it being extensible by anyone other than the developers.
34
Figure 7.1: Parts Rendered with the Wrong Vertex Ordering
A better approach would have been to make the smoothing methods plug-ins tothe system, and therefore allowing anyone to write them. The system could thenload the plug-in and supply the user with that smoothing option, if the plug-in wasremoved the option would be removed from the user.
7.4 Smoothing
Due to the time constraints and unforseen problems during the mesh creation withthe internal parts and variations in the mesh density becoming a problem, only alimited number of smoothing methods could be implemented and tested. These maynot be the best methods to be applied to LEGO objects, and exploration of othersmoothing methods, or their variations could lead to better methods. Howeverthe system is able to take LEGO objects, and once the necessary fixes are fullyimplemented to the mesh creation procedure, create a mesh structure, and this canbe used as a basis for the exploration of other smoothing methods.
35
Chapter 8
Further Work
This section outlines areas in which this project could be further developed.
Small additions to the system would be; complete the implementation of removinginternal parts from the mesh, to create a mesh of equal density, and to implementthe DirectX File output allowing the smoothed models to be used within other ap-plications.
Other more substantial areas of development are;
• To adapt the system to provide more extensibility for different smoothingmethods.
• To use the system as a basis for investigating better or other methods ofsmoothing or otherwise, that can remove the ‘blocky’ appearance of the Legostructures.
• To improve the smoothing technique so rather than applying it to the modelas a whole, certain parts of the model could be selected to be smoothed whileother areas are left unchanged.
• To investigate methods that can reduce the polygon count of the smoothed ob-jects. For the smoothing to work well a reasonable number of subdivision arerequired. For example the test of Loop’s masking carried out five subdivisionson the pyramid object, creating a mesh consisting of 307,200 polygons. De-pending on the domain of the application which may wish to use this smoothedobject this number of polygons could be unfeasible, and methods to reduce thepolygon count while still keeping the smoothed appearance would be required.
36
Chapter 9
Conclusion
The project set out to investigate if LEGO models could be used as a feasiblemethod of creating 3D objects for multiple domains. This requires the objects shouldloose their noticeable LEGO characteristics of studs, and ‘blocky’ shape. The studsbecame an easy feature to remove due to the hierarchical structure contained withinthe LDraw file format. The ‘blocky’ appearance then became the main area ofinterest for the project. Methods of smoothing were researched, and it was found amesh of the object would be required. A method for converting the LEGO objectto a mesh was implemented, and new problems of internal parts, and varied meshdensity were found. A method to identify and remove the internal parts from themesh was then drawn up, and implementation attempted. Unfortunately due totime constraints this could not be completed, but some examples could be testedand the results showed the positive effects of removing these parts. Three smoothingmethods were investigated and implemented, and while being adversely affected bythe problems with the mesh they were still able to show signs of smoothing, withLaplacian giving reasonable results under the conditions. This project was unableto determine a perfect method of smoothing all LEGO objects, and more researchand implementation is needed before such a statement could be made. However itprovides a basis for this research and shows that the creation of smooth 3D objectsfrom LEGO building blocks is likely to be possible.
37
Bibliography
[1] Efficient, fair interpolation using catmull-clark surface, SIGGRAPH’93. Com-puter Graphics, August 1993.
[2] Lego website.URL http://www.lego.com/eng/factory/, Accessed: December, 2005.
[3] Qube software website.URL http://www.qubesoft.com/, Accessed: December, 2005.
[4] T. Bulow. Spherical diffusion for 3d surface smoothing. IEEE Transactions onPattern Analysis and Machine Intelligence, 26(12), December 2004.
[5] LDraw Committe. Ldraw file format.URL http://www.ldraw.org/modules.php?op=modload&name=News&file=article&sid=45, Accessed: December, 2005.
[6] LDraw Committee. Ldraw organistation website.URL http://www.ldraw.org, Accessed: April, 2006.
[7] LDraw Committee. Ldraw compatable lego simultors.URL http://www.ldraw.org/modules.php?op=modload&name=Downloads&file=index&req=viewdownload&cid=8, Accessed: December, 2005.
[8] L. Dupont. C# graphics library.URL http://csgl.sourceforge.net/index.html, Accessed: December 2005.
[9] C. LOOP. Smooth subdivision surfaces based on triangles. Master’s thesis,Dept. of Mathematics, University Of Utah, August 1987.
[10] B. McPhail. Loop’s subdivision.URL http://www.reed.edu/ mcphailb/opengl/proj2/, Accessed: December2005.
[11] Microsoft. Directx file format reference.URL http://msdn.microsoft.com/library/default.asp?url/̄library/en-us/directx9 c/directx/graphics/reference/fileformat/xfileformat.asp,Accessed: December 2005.
[12] Wolframe Research. Mathworld.URL http://mathworld.wolfram.com/, Accessed: April 2006.
[13] Matthew L. Staten Scott A. Cannann, Joseph R. Tristano. An approach to com-bined laplacian and optimization-based smoothing for triangular, quadrilateral,and quad-dominant meshes. Technical report, ANSYS, Inc.
38
[14] I. Sommerville. Software Engineering. Addison-Wesley, 6th edition, 2001.
[15] G Taubin, editor. A signal processing approach to fair surface design, Computergraphics and interactive techniques. ACM Press, 1995.
39
Appendix A
File Formats
A.1 LDraw File Format
The LDraw file specification is shown below. This is taken from the specificationdocument supplied by LDRAW [5]. Key information is listed first then followed bydescriptions of available commands.
• File Extension (LDR or DAT)
• Plain Text
• Each line is a new command
• Each line is independent
• Only a few commands are used
• Commands are identified by the first number on a line (Known as the line-type)
• Contents and format of a line depend on the line-type
• Commands
0. Comment or Meta-command
1. Part file reference
2. Line
3. Triangle
4. Quadrilateral
5. Draws a line between the first two points, if the last two points are onthe same side of that line
• If the line type is not valid then the line is ignored
• Meta commands require a key word to follow the line-type. Key words must bein CAPITALS. If the meta-command does not require additional information,none should be given.
40
• Meta-commands
Model Title
Step
Write
Clear
Pause
Save
File Type
BFC CW
BFC CCW
BFC INVERTNEXT
Below are descriptions of each of the commands and meta-commands. Commandsare instructions on how to display a shape. Meta-commands are instructions tothe simulation software telling it to carry out a task. The meta-commands are notimplemented in every simulation system. Additional ones are sometimes added byother systems. The ones listed here are the official ones supported by LDraw’s ownsimulation system.
Colour is used in most commands, so to avoid duplication of the definition of colourit is shown here.
• colour is a colour code: 0-15, 16, 24, 32-47, 256-511
0 = Black1 = Blue2 = Green3 = Dark Cyan4 = Red5 = Magenta6 = Brown7 = Grey8 = Dark Grey9 = Light Blue10 = Light Green11 = Cyan12 = Light Red13 = Pink14 = Yellow15 = White16 = Current Colour24 = Complementary Colour to the current colour32 - 47 = Transparent Colours256 - 511 = Dithered Colour.
To obtain a transparent colour add 32 to the original colour.To use dithered colour such as to combine colours J and K. The colour value specifiedshould be:
41
colour = (J * 16) + K + 256
Part Command
Inserts a part defined in another LDraw file.Line format:
1 colour x y z a b c d e f g h i part.dat
• x, y, z is the position of the part
• a - i are orientation & transformation parameters
• parts.dat is the filename of the included part
Part files can also include Part commands, there is not a limit on the depth ofreferencing parts.The letters x, y, z, a - i represent a homogenous matrix transformation that can beapplied to the include part. They are arranged as such:
a d g 0b e h 0c f i 0x y z 1
Note only rotations and translations can be applied as scaling variables are notadjustable.
Line Command
Draws a line between two points.Line format:
2 colour x1 y1 z1 x2 y2 z2
• x1, y1, z1 is the position of the first point
• x2, y2, z2 is the position of the second point
Triangle Command
Draws a filled triangle between three points.Line format:
3 colour x1 y1 z1 x2 y2 z2 x3 y3 z3
• x1, y1, z1 is the position of the first point
• x2, y2, z2 is the position of the second point
• x3, y3, z3 is the position of the third point
42
Quadrilateral Command
Draws a four-sided, filled shape between four points.Line format:
4 colour x1 y1 z1 x2 y2 z2 x3 y3 z3 x4 y4 z4
• x1, y1, z1 is the position of the first point
• x2, y2, z2 is the position of the second point
• x3, y3, z3 is the position of the third point
• x4, y4, z4 is the position of the fourth point
Conditional-Line Command
Draws a line between the first two points, if the projections of the last two pointsonto the screen are on the same side of an imaginary line through the projections ofthe first two points onto the screen.Line format:
5 colour x1 y1 z1 x2 y2 z2 x3 y3 z3 x4 y4 z4
• x1, y1, z1 is the position of the first point
• x2, y2, z2 is the position of the second point
• x3, y3, z3 is the position of the third point
• x4, y4, z4 is the position of the fourth point
Model Title
The descriptive name of the model or part file.Line Format:
0 title-text
Where title-text is the name of the model. This is a location specific command. I.e.If the first line has a line-type of 0 then that line is the title. This overrides anymeta-commands on the line.
Step Meta-Command
Marks the end of a building step.Line Format:
0 STEP
The command will cause LDraw to stop until <Enter> is pressed. It will causeLDraw to save the current image into a bitmap file.
43
Write Meta-Command
Display a message at the top of the screen.Line Format:
0 WRITE message-text [or] PRINT message-text
Message-text is the message to be displayed.
Clear Meta-Command
Clears the screen.Line Format:
0 CLEAR
Useful for advanced model files.
Pause Meta-Command
Causes LDraw to stop until you press <Enter>.Line Format:
0 PAUSE
The is like the Step command, but does not save bitmaps.
Save Meta-Command
Causes LDraw to save the current image in a bitmap.Line Format:
0 SAVE
This saves the current image to a bitmap.
File Type Meta-Command
Indicates the type of file (model, part, etc), and if it is part of the LDraw.org PartsLibrary.Line Format:
0 LDRAW_ORG type
OR
0 Official LCAD type
OR
0 Unofficial type
OR
0 Un-official type
44
This meta statement should appear as the last line of the file header. The firstformat (LDRAW_ORG) is the current standard for the official parts library. Olderfiles used the second (Official LCAD) format. The valid values for the type field are:
• Part
• Element
• Sub-Part
• Primitive
• Shortcut
• Alias
• Model
• Submodel
• File
• Hi-Res Primitive
BFC CW Meta-Command
Indicates the triangles and quadrilaterals defined in the part file have their verticesspecified such that when the shape is facing forwards they are in a clock-wise direc-tion. This information is needed when rendering using 3D renders such as OpenGL.
BFC CCW Meta-Command
Indicates the triangles and quadrilaterals defined in the part file have their verticesspecified such that when the shape is facing forwards they are in a counter clock-wisedirection.
BFC INVERT NEXT Meta-Command
Indicates that the part defined on the next line is to be inverted i.e. if the defined partspecifies it is clock-wise ordered then it should now be counter clock-wise orderedand vis versa.
A.2 DirectX File Format
The DirectX file specification is shown below. This is taken from the specificationdocument supplied by Microsoft [11]. There are 4 types of directX file as listedbelow, however the description is that of the text type. Key information is listedfirst then followed by descriptions of instructions, data structures, and templates.
• File extension is .x
• File type can be, plain text, binary, MSZip compressed text, or MSZip com-pressed binary
45
• Template based
• Uses data structures
• Variable length header, defining:
File type
Version number
Floating point representation
Magic number
Comments
Comments can occur anywhere in the file. They either begin with # or // and runto the end of the line.
Reserved Words
Words that are reserved and used by the directX language are:
• ARRAY
• BINARY
• BINARY_RESOURCE
• CHAR
• CSTRING
• DOUBLE
• DWORD
• FLOAT
• SDWORD
• STRING
• SWORD
• TEMPLATE
• UCHAR
• ULONGLONG
• UNICODE
• WORD
Variable-length Header
The variable-length header is compulsory and must be at the beginning of the datastream. It contains the following data:
46
Table A.1: Contents of Variable Length Header. [11]
Type Size (bytes) Value DescriptionMagic Number 4 ”xof”Version Number 2 ”03” Major Version 3
”03” Minor Version 3Format Type 4 ”txt ” Text File
”bin ” Binary File”tzip” MSZip compressed text file””bzip” MSZip compressed binary file
Float Size 4 ”0064” 64-bit floats”0032” 32-bit floats
Data
Data objects contain the actual data or a reference to that data. Each data objecthas a corresponding template that specifies its data type.
Form, Identifier, and Name
Data objects have the following form.<Identifier> [name] { [<UUID>]
<member 1>;...<member n>;
}
The identifier is compulsory and must match a previously defined data type orprimitive. The name, however, is optional.
Data Members
Data members are either one of the following: data object, data reference, integerlist, float list, or string list.A data object is a nested data object, allowing the hierarchical nature of the fileformat to be expressed. A data reference is a reference to a previously encountereddata object, as shown below:{
name |UUID |name UUID
}Integer, float, and string lists are semicolon delimited. For example, integer list:Integer List: 1; 2; 3;Float List: 1.0; 2.0; 3.0;String List: "One"; "Two"; "Three";
47
Templates
Templates define how the data stream is interpreted.There is one special template called the header template. This can store applicationspecific information such as version numbers. A flags member can also be definedas a DWORD. If clear (i.e. equals 0) then the following data in the file is binary, ifits set (i.e equals 1) then the following data is text. Multiple header data objectscan be used to switch between binary and text format within the file.
Template Form, Name, and UUID
A template has the flowing form.template <template-name> {<UUID>
<member 1>;...<member n>;
[restrictions]}template-name is alphanumeric and can contain the underscore character (_). Itmust not start with a digit. The UUID is a universally unique identifier formattedto the Open Software Foundation’s Distributed Computing Environment standardand enclosed by angle brackets (< >).For example: <34A3066F-AE1F-4410-B7DC-6DFBB6DC82D7>
Template Members
Template members are a named data type followed by an optional name, or an arrayof a named type.
Table A.2: Valid primitive data types. [11]
Type SizeWORD 16 bitsDWORD 32 bitsFLOAT IEEE floatDOUBLE 64 bitsCHAR 8 bitsUCHAR 8 bitsBYTE 8 bitsSTRING NULL terminated stringCSTRING Formatted C string (not supported)UNICODE Unicode string (not supported)
Additional data types defined by templates can be used, but these must be definedabove the point they are being used. No forward references are allowed.Any valid data type can be expressed as an array in the template definition.
array <data-type> <name>[<dimension-size>];
48
<dimension-size> can be either an integer or named reference to another tem-plate member whose value is then substituted. Arrays can be multidimensional.The number of dimensions is determined by the number of paired square bracketstrailing the statement. For example:
array DWORD FixedHerd[24];array DWORD Herd[nCows];array FLOAT Matrix4x4[4][4];
Template Restrictions
Templates can be open, closed, or restricted. These restrictions determine whichdata types can appear in the data object the template defines. An open templatehas no restrictions, a closed template rejects all template defined data types, and arestricted template only allows those specified.Open template: [ ... ]Restricted template: [ { data-type [ UUID ] , } ... ]The absence of these two indicate a closed template.
Template Example
template Mesh {<34A3066F-AE1F-4410-B7DC-6DFBB6DC82D7>DWORD nVertices;array Vector vertices[nVertices];DWORD nFaces;[ ... ] //An open template}
template Vector {<39G4829D-FK59-DJF9-384K-JJ49FLDS8FL0>FLOAT x;FLOAT y;FLOAT z;} //a closed template
template FileSystem {<UUID>STRING name[ Directory <UUID>, File <UUID> ] //A restricted template}
49
Appendix B
Part Structures
These are the definitions for the object representation used in the system for thefive different part types declared in the LDraw file format.
• Base Part Object (which all parts are inherited from)
Type Stores the type of the object, is redundant as the type can be derivedfrom the type of the object, but is quicker to test against in computingterms.
Colour The LDraw colour code of the part.
RealColour The actual RGB colour that will be rendered.
• Part Type
Part The referenced part stored as another LEGO object.
Transformation Matrix The matrix defined in the LDraw file specifyinghow the referenced part should be scaled, and rotated.
Stud Is a boolean flag that is set to true during the loading if the part namecontains the word stud. This is used for stud removal.
Inverted Is a boolean flag used to specify it the referenced part should beinverted.
Name The name of the part, used as the key in the hash table of alreadyloaded parts.
• Line Type
P1 Start point of the line
P2 End point of the line
• Triangle Type
P1 First vertex of the triangle
P2 Second vertex of the triangle
P3 Third vertex of the triangle
Norm The normal to the surface of the triangle
50
• Quadrilateral Type
P1 First vertex of the quadrilateral
P2 Second vertex of the quadrilateral
P3 Third vertex of the quadrilateral
P4 Forth vertex of the quadrilateral
Norm The normal to the surface of the quadrilateral
• Special Line Type
LineP1 Start point of the line
LineP2 End point of the line
TestP1 Start point of conditional line
TestP2 End point of the conditional line
51
Appendix C
Overlap Resolving Images
Figure C.1: Overlap Resolving for External Vertices and Line Intersections
Image 1 shows where the overlapping sections of the bright red and green quadrilat-erals have been removed. Image 2 shows the same process on the other overlappingquadrilateral. The bold black lines show where the line intersections were and howthey were joined.
Figure C.2: Overlap Resolving for External Vertices and Intersections
The bright blue area shows how one quadrilateral can be inside the other quadrilat-eral, shown here by its green outline. The bright red section is the only part thatremains after the overlap has been resolved.
52
Figure C.3: Overlap Resolving for Internal Vertices
Image 1 shows the overlap of the two bricks in 3D giving an example of when thiscase can occur. Image 2 shows this again from the top and only displaying the twoquadrilaterals we are concerned with. Image 3 shows the two newly created shapesin red and gray. Taking the red shape, the red circles show its external vertices, theblue its internal vertex, and the green the line intersections. The red lines show howthe externals vertices and line interceptions are connected, and the blue lines showhow the internal vertex and line interceptions are connected. As can be seen a 5vertex shape has been created.
53
Appendix D
Code Listing
Below is the complete code listing for the system.
D.1 Common.vb
This file holds useful calculations for example the vector mathematics.1 Imports LEGO_Smoothing.ModelDefs
23 Public Class Common
45 Public Shared GlobID As Integer = 0
67 Public Class Point3D
8 Public x, y, z As Single
9 Public Sub New(ByVal _X As Single , ByVal _Y As Single , ByVal _Z As Single)
10 x = _X
11 y = _Y
12 z = _Z
13 End Sub
1415 Public Function Copy() As Point3D
16 Return New Point3D(x, y, z)
17 End Function
1819 Public Sub New()
20 End Sub
2122 Public Sub Clear()
23 x = 0
24 y = 0
25 z = 0
26 End Sub
2728 Public Shadows Function Equals(ByVal p As Point3D) As Boolean
29 Return x = p.x AndAlso y = p.y AndAlso z = p.z
30 End Function
3132 Public Function IntEquals(ByVal p As Point3D) As Boolean
33 ’Carries out the check but rounds the values to integers , due to the calculations used to find intersections
34 ’these can sometimes come out as close approximations , i.e. instead of 20 they give 19.999999999998.
35 ’These should be the same , so intEquals would return true for this case.
36 If Not (Single.IsNaN(x) AndAlso Single.IsNaN(y) AndAlso Single.IsNaN(z)) Then
37 Return CInt(x) = CInt(p.x) AndAlso CInt(y) = CInt(p.y) AndAlso CInt(z) = CInt(p.z)
38 Else
39 Return False
40 End If
41 End Function
42 End Class
4344 Public Class Matrix
45 Public matrix(,) As Single
46 Public Identity As Boolean
4748 Public Sub New()
49 ’This is a matrix class designed just for the loading of the lego models
50 ’so will only be used for loading the part files , and only requires 3x3
51 ’matrices
52 ReDim matrix(2, 3)
53 Identity = False
54 End Sub
5556 Public Sub New(ByVal data(,) As Single)
57 ’Can fill it with a different matrix
58 matrix = data
59 Identity = CheckIdentity ()
60 End Sub
54
6162 Public Sub New(ByVal part() As String , ByVal startIndex As Integer)
63 ’For speed and memory doesn ’t bother to store the 0,0,0,1 column of the homegenious matrix
64 ’Creates the matrix directly from the string array obtained from the lego file.
6566 ’Takes the string array and the start and finish of it and will create a square matrix.
67 ’Fills it in top to bottom left to right (i.e. goes down the 1st column then down the 2nd , then down the 3rd)
6869 ReDim matrix(2, 3)
70 For x As Integer = 0 To 2
71 For y As Integer = 0 To 2
72 ’The + 3 moves over the x,y,z values which are added at the end
73 matrix(x, y) = CSng(part(startIndex + 3 + (x * 3) + y))
74 Next
75 Next
76 For x As Integer = 0 To 2
77 matrix(x, 3) = CSng(part(startIndex + x))
78 Next
7980 Identity = CheckIdentity ()
81 End Sub
8283 Private Function CheckIdentity () As Boolean
84 Return matrix(0, 0) = 1 AndAlso matrix(1, 1) = 1 AndAlso matrix(2, 2) = 1 AndAlso _
85 matrix(0, 1) = 0 AndAlso matrix(0, 2) = 0 AndAlso _
86 matrix(1, 0) = 0 AndAlso matrix(1, 2) = 0 AndAlso _
87 matrix(2, 0) = 0 AndAlso matrix(2, 1) = 0
88 End Function
8990 Public ReadOnly Property IsReversed () As Boolean
91 Get
92 ’Reversed Matrices are those which have a negative determinant .
93 ’Remove the x,y,z entries on the bottom row of the matrix , as these aren ’t needed for this calculation
94 Dim tmpMatrix (2, 2) As Single
95 For c1 As Integer = 0 To 2
96 For c2 As Integer = 0 To 2
97 tmpMatrix(c1, c2) = matrix(c1, c2)
98 Next
99 Next
100 Return Determinant(tmpMatrix) < 0
101 End Get
102 End Property
103104 Private Function Determinant(ByVal Mat(,) As Single) As Single
105 ’Just needs to be done for a 3x3 matrix in this application , so hard codes a lot of the calculated values
106 ’such as -ve or +ve signs , and recursive nature of finding each determinate as these are known for a fixed
107 ’matrix size , and is faster done hard coded to 3x3 matrix size.
108109 Return (Mat(0, 0) * ((Mat(1, 1) * Mat(2, 2)) - (Mat(2, 1) * Mat(1, 2)))) _
110 - (Mat(1, 0) * ((Mat(0, 1) * Mat(2, 2)) - (Mat(2, 1) * Mat(0, 2)))) _
111 + (Mat(2, 0) * ((Mat(0, 1) * Mat(1, 2)) - (Mat(1, 1) * Mat(0, 2))))
112 End Function
113114 End Class
115116 Public Enum eVertexDirection
117 Clockwise
118 CounterClockWise
119 End Enum
120121 Public Shared Function CalcNormal(ByVal Points As Point3D ()) As Point3D
122 ’Triangle or Quadralatural - Count the points in the array
123 Dim p1, p2 As Point3D
124 Dim ret As Point3D = Nothing
125 Select Case Points.Length
126 Case 3 ’Triangle
127 p1 = SubVector(Points (1), Points (0))
128 p2 = SubVector(Points (2), Points (0))
129 ret = CrossProductVector(p1 , p2)
130 Case 4 ’Quad
131 p1 = SubVector(Points (1), Points (0))
132 p2 = SubVector(Points (3), Points (0))
133 ret = CrossProductVector(p1 , p2)
134 End Select
135 If Not ret Is Nothing Then
136 Return NormaliseVector(ret)
137 Else
138 Return Nothing ’ERROR incorrect shape
139 End If
140 End Function
141142 Public Shared Function NormaliseVector(ByVal vec As Point3D) As Point3D
143 Dim res As Point3D
144 With vec
145 Dim scale As Single = CSng(1 / MagnitudeVector(vec))
146 res = New Point3D (.x * scale , .y * scale , .z * scale)
147 End With
148 Return res
149 End Function
150151 Public Shared Function MagnitudeVector(ByVal vec As Point3D) As Double
152 With vec
153 Return Math.Sqrt ((.x ^ 2) + (.y ^ 2) + (.z ^ 2))
154 End With
155 End Function
55
156157 Public Shared Function CrossProductVector(ByVal a As Point3D , ByVal b As Point3D) As Point3D ’=a x b
158 Dim C As New Point3D
159 C.x = a.y * b.z - a.z * b.y
160 C.y = a.z * b.x - a.x * b.z
161 C.z = a.x * b.y - a.y * b.x
162 Return C
163 End Function
164165 Public Shared Function CrossProductMagnitude(ByVal a As Point3D , ByVal b As Point3D) As Double ’=|a x b|
166 Return Math.Abs(MagnitudeVector(a)) * Math.Abs(MagnitudeVector(b)) * _
167 Math.Sqrt(1 - (DotProductVector(NormaliseVector(a), NormaliseVector(b))) ^ 2)
168 End Function
169170 Public Shared Function DotProductVector(ByVal a As Point3D , ByVal b As Point3D) As Double ’= a . b
171 Return (a.x * b.x) + (a.y * b.y) + (a.z * b.z)
172 End Function
173174 Public Shared Function SubVector(ByVal a As Point3D , ByVal b As Point3D) As Point3D ’= a - b
175 Return New Point3D(a.x - b.x, a.y - b.y, a.z - b.z)
176 End Function
177178 Public Shared Function AddVector(ByVal a As Point3D , ByVal b As Point3D) As Point3D ’= a - b
179 Return New Point3D(a.x + b.x, a.y + b.y, a.z + b.z)
180 End Function
181182 Public Shared Function ScaleVector(ByVal p As Point3D , ByVal scaler As Single) As Point3D
183 Return New Point3D(p.x * scaler , p.y * scaler , p.z * scaler)
184 End Function
185186 Public Enum eInstruction
187 Line
188 Triangle
189 Quadrilateral
190 Colour
191 Normal
192 End Enum
193194 Public Enum eInterceptType
195 None
196 Intercept
197 Internal
198 End Enum
199200 Public Enum eSmoothMethod
201 Taubin
202 [Loop]
203 Laplacian
204 End Enum
205206 Public Structure InstructionSet
207 Dim Instructions () As eInstruction
208 Dim Values () As Single
209 End Structure
210211 Public Shared Function MakeTriangles(ByVal Quad As Part_Quadrilateral) As Point3D ()()
212 ’Triangles defined in this order of vertices , so the devide line through the
213 ’middle of the quad is defined by the first and last vertex of the triangle
214 ’this allows me to check if a point is on the diagonal line , and therefore
215 ’actually internal , and not intercepting in the DoesIntercept function
216 Dim t1() As Point3D = New Point3D () {Quad.P1 , Quad.P2, Quad.P3}
217 Dim t2() As Point3D = New Point3D () {Quad.P3 , Quad.P4, Quad.P1}
218 Dim ret ()() As Point3D = New Point3D ()() {t1 , t2}
219 Return ret
220 End Function
221222 Public Shared Sub RemoveDuplicates(ByRef arr As ArrayList)
223 Dim i As Integer = 0
224 Dim i2 As Integer = 0
225 While i < (arr.Count - 1)
226 i2 = i + 1
227 While i2 < (arr.Count)
228 If DirectCast(arr(i), Point3D ). Equals(DirectCast(arr(i2), Point3D )) Then
229 arr.RemoveAt(i2)
230 i2 -= 1
231 End If
232 i2 += 1
233 End While
234 i += 1
235 End While
236 End Sub
237238 End Class
56
D.2 datReader.vb
The file parser used to load the LEGO LDraw files into the internal representation.1 Imports LEGO_Smoothing.Common
2 Imports LEGO_Smoothing.ModelDefs
34 Public Class datReader
56 Private Shared loadedModels As ModelDefs.LegoModels
78 Public Shared Function LoadLegoFile(ByVal LDrawFolder As String , ByVal Filename As String) As LegoModel
9 loadedModels = New LegoModels ’Clears the model information
10 Dim lm As LegoModel = ReadLEGOFile(LDrawFolder , Filename , False)
11 Transformer.TransformModel(lm)
12 Return lm
13 End Function
1415 Private Shared Function ReadLEGOFile(ByVal LDrawFolder As String , ByVal Filename As String , _
16 ByVal Invert As Boolean) As LegoModel
1718 Dim fullName As String = Filename
19 Dim InvertNext As Boolean = False
20 Dim InvertNextCount As Integer = 0
2122 Dim mVertexDir As eVertexDirection = eVertexDirection.CounterClockWise ’Files default to counterclockwise
2324 If Not IO.Directory.Exists(LDrawFolder) Then
25 MsgBox("Invalid Lego Part Folder: " & LDrawFolder , MsgBoxStyle.OkOnly Or MsgBoxStyle.Critical , "Error")
26 Return Nothing
27 End If
2829 ’Check if the filename is is a complete path to the model , or just the name of a part.
30 ’If it is a complete path and exists - just use it. Else try and find it in the parts folders
3132 If Not IO.File.Exists(fullName) Then
33 ’Folder structure allows for lego parts to be either in a folder named "P"
34 ’or a folder named "PARTS" withing the LDrawFolder . So need to check where it is
35 fullName = LDrawFolder & "\P\" & Filename
36 If Not IO.File.Exists(fullName) Then
37 fullName = LDrawFolder & "\PARTS\" & Filename
38 If Not IO.File.Exists(fullName) Then
39 Return Nothing
40 End If
41 End If
42 End If
4344 Dim name As String = New IO.FileInfo(fullName ).Name
45 Dim sr As New IO.StreamReader(fullName)
46 Dim retModel As New LegoModel
4748 While Not sr.Peek = -1
49 Dim line As String = sr.ReadLine.ToLower
50 If Not (line Is Nothing OrElse line.Trim = "") Then
51 Dim parts () As String = line.TrimStart.Split(" "c)
52 parts = SortParts(parts) ’removes "" entries where multiple spaces have been used
5354 Select Case (parts (0))
55 Case ""
56 ’Empty lines can be ignored
5758 Case "0" ’Comment / Meta Command
59 ’Some meta commands may be impletmented
6061 ’Detect if shape declared counter clockwise or clockwise.
62 ’OPENGL supports counter clockwise
63 If line.Contains("bfc") Then
64 If line.Contains("ccw") Then ’Following shapes in counterclockwise vertex order
65 mVertexDir = eVertexDirection.CounterClockWise
66 ElseIf line.Contains("cw") And Not line.Contains("ccw") Then
67 ’Following shapes in Clockwise vertex order
68 mVertexDir = eVertexDirection.Clockwise
69 ElseIf line.Contains("invertnext") Then
70 InvertNext = True
71 InvertNextCount = 0
72 End If
73 End If
7475 Case "1" ’Part - Files that represent other lego parts/models + colour + transformation
7677 Dim lp As New Part_External
78 lp.colour = CInt(parts (1))
79 lp.transformMatrix = New Matrix(parts , 2)
80 Dim matrixReversed As Boolean = lp.transformMatrix.IsReversed
81 InvertNext = (InvertNext Xor matrixReversed) Xor Invert
82 ’InvertNext = (InvertNext Xor lp.transformMatrix.IsReversed) Xor Invert
83 ’Recursive call to load the lego model described by the part file
84 ’Checks if the part has already been loaded before.
85 ’If so will use the already loaded version.
86 lp.inverted = InvertNext
8788 ’InvertNext and the counter InvertNextCount works when the line before a part specifies
89 ’invert next. However if the matrix of the current line says to invert , then want to make
90 ’sure invert next is droped before the next line after this. So incrementing the
57
91 ’invertnextcount will make this work
9293 If matrixReversed Then InvertNextCount += 1
9495 If Not loadedModels.ModelExist(parts (14)) Then
96 Dim tmpModel As LegoModel = ReadLEGOFile(LDrawFolder , parts (14), InvertNext)
97 If tmpModel Is Nothing Then
98 MsgBox("Error loading referenced part:" & parts (14))
99 ’Won ’t add the part if it can ’t load
100 Else
101 loadedModels.addModel(parts (14), tmpModel)
102 lp.part = tmpModel
103 End If
104 Else
105 lp.part = loadedModels.getModel(parts (14))
106 End If
107 lp.Stud = name.ToLower.Contains("stud")
108 lp.Name = name.ToLower
109 retModel.addPart(lp)
110111 Case "2" ’Line - Defined by two points + colour
112 Dim lp As New Part_Line
113 lp.colour = CInt(parts (1))
114 lp.P1 = SortPoint3D(parts , 2)
115 lp.P2 = SortPoint3D(parts , 5)
116 retModel.addPart(lp)
117118 Case "3" ’Triangle - Defined by 3 points + colour
119 Dim lp As New Part_Triangle
120 lp.colour = CInt(parts (1))
121122 If (mVertexDir = eVertexDirection.Clockwise) Then
123 lp.P1 = SortPoint3D(parts , 8)
124 lp.P2 = SortPoint3D(parts , 5)
125 lp.P3 = SortPoint3D(parts , 2)
126 Else
127 lp.P1 = SortPoint3D(parts , 2)
128 lp.P2 = SortPoint3D(parts , 5)
129 lp.P3 = SortPoint3D(parts , 8)
130 End If
131132 ’lp.Norm = Common.CalcNormal(Invert , New Point3D () {lp.P1, lp.P2, lp.P3})
133 retModel.addPart(lp)
134135 Case "4" ’Quadrilateral - Defined by 4 points + colour
136 Dim lp As New Part_Quadrilateral
137 lp.colour = CInt(parts (1))
138139 If (mVertexDir = eVertexDirection.Clockwise) Then
140 lp.P1 = SortPoint3D(parts , 8)
141 lp.P2 = SortPoint3D(parts , 5)
142 lp.P3 = SortPoint3D(parts , 2)
143 lp.P4 = SortPoint3D(parts , 11)
144 Else
145 lp.P1 = SortPoint3D(parts , 2)
146 lp.P2 = SortPoint3D(parts , 5)
147 lp.P3 = SortPoint3D(parts , 8)
148 lp.P4 = SortPoint3D(parts , 11)
149 End If
150151152 retModel.addPart(lp)
153154 Case "5" ’Special Line - Defined by 4 points + colour
155 Dim lp As New Part_SpecialLine
156 lp.colour = CInt(parts (1))
157 lp.LineP1 = SortPoint3D(parts , 2)
158 lp.LineP2 = SortPoint3D(parts , 5)
159 lp.TestP1 = SortPoint3D(parts , 8)
160 lp.TestP2 = SortPoint3D(parts , 11)
161 retModel.addPart(lp)
162163 End Select
164 End If
165166 If InvertNext Then InvertNextCount += 1
167 If InvertNext AndAlso InvertNextCount > 1 Then InvertNext = False
168 End While
169170171 sr.Close ()
172173 Return retModel
174175 End Function
176177 Private Shared Function SortParts(ByVal Parts () As String) As String ()
178 If Parts.Length = 0 Then Return Parts
179 Dim al As New ArrayList
180 For Each s As String In Parts
181 If Not s = "" Then al.Add(s)
182 Next
183 Return DirectCast(al.ToArray(GetType(String)), String ())
184 End Function
185
58
186 Private Shared Function SortPoint3D(ByVal Parts() As String , ByVal startIndex As Integer) As Point3D
187 ’Takes the parts string array loaded from the current line in the file , and
188 ’then loads the startindex and next 2 of the items into a new point3D object
189 Return New Point3D(CSng(Parts(startIndex )), CSng(Parts(startIndex + 1)), CSng(Parts(startIndex + 2)))
190 End Function
191192 End Class
59
D.3 Form1.vb
The main form which the user controls the program from. It is the central partof the program controlling the calling of loading, mesh conversion subdivision andsmoothing.
1 Imports LEGO_Smoothing.Common
23 Public Class Form1
4 Inherits System.Windows.Forms.Form
56 Private WithEvents mView As OpenGLViewer
7 Private thrOpenGL As Threading.Thread
8 Private mLegoModel As ModelDefs.LegoModel
9 Private mUnsurfacedLegoModel As ModelDefs.LegoModel
10 Private mUnsmoothedModel As ModelDefs.LegoModel
1112 Protected Overrides Sub OnLoad(ByVal e As System.EventArgs)
13 MyBase.OnLoad(e)
14 ’Hard coded part folder - just for speed. Could become a registry entry in the future
15 lblPartFolder.Text = "C:\ Documents and Settings\Michael Ball\My Documents\Uni\Year 3\ Semester 2\" _
16 + "CM30082 - Double Module Project\Lego Parts"
17 mView = New OpenGLViewer
18 mView.Parent = pnlOpenGLHolder
19 mView.Dock = DockStyle.Fill
20 mView.AllowDrop = True
21 End Sub
2223 Private Sub cmdLoadModel_Click(ByVal sender As System.Object , ByVal e As System.EventArgs) _
24 Handles cmdLoadModel.Click
2526 If New IO.DirectoryInfo(lblPartFolder.Text). Exists Then
27 Dim file As New OpenFileDialog
28 file.CheckFileExists = True
29 file.CheckPathExists = True
30 file.ShowHelp = False
31 file.ValidateNames = True
32 file.Title = "Select Model To Load"
33 file.InitialDirectory = lblPartFolder.Text
34 file.Filter = "Lego Files (*.ldr; *.dat )|*. ldr ;*.dat"
35 file.Multiselect = False
36 If file.ShowDialog(Me) = Windows.Forms.DialogResult.OK Then
37 LoadModel(file.FileName)
38 End If
39 Else
40 MsgBox("Invalid Part Folder", MsgBoxStyle.OkOnly Or MsgBoxStyle.Critical , "Error")
41 End If
42 End Sub
4344 Private Sub LoadModel(ByVal FileName As String)
45 If Not thrOpenGL Is Nothing Then thrOpenGL.Abort () ’Stop the current display of the current model
46 mLegoModel = datReader.LoadLegoFile(lblPartFolder.Text , FileName)
47 If mLegoModel Is Nothing Then
48 MsgBox("ERROR LOADING MODEL: FileName")
49 Else
50 ’Used to confirm loading , then display model button would be pressed , now it just displays if loading
51 ’was successful
52 updateInstructionSet ()
53 thrOpenGL = New Threading.Thread(AddressOf OpenGL_Display)
54 thrOpenGL.Start()
55 End If
56 mUnsurfacedLegoModel = mLegoModel
57 chkOuterShell.Checked = False
58 End Sub
5960 Private Sub cmdPart_Click(ByVal sender As System.Object , ByVal e As System.EventArgs) Handles cmdPart.Click
61 Dim fldr As New FolderBrowserDialog
62 fldr.Description = "Select Lego Parts Folder"
63 fldr.ShowNewFolderButton = False
64 If fldr.ShowDialog(Me) = Windows.Forms.DialogResult.OK Then
65 If Not thrOpenGL Is Nothing Then thrOpenGL.Abort() ’Stop the current display of the current model
66 lblPartFolder.Text = fldr.SelectedPath
67 End If
68 End Sub
6970 Private FPSstartTime As DateTime = Now
71 Private FPSCount As Integer = 0
7273 Delegate Sub UpdateFPSTextCallBack(ByVal text As String)
7475 Private Sub UpdateFPSText(ByVal text As String)
76 If Me.lblFPS.InvokeRequired Then
77 Try
78 Dim d As New UpdateFPSTextCallBack(AddressOf UpdateFPSText)
79 Me.Invoke(d, New Object () {text})
80 Catch ex As Exception
81 ’Only errors on close when there is no object to invoke. I.e. the main form has closed.
82 ’Therefore don ’t care about the error , as the program is no longer really running.
83 End Try
84 Else
85 lblFPS.Text = text
86 End If
60
87 End Sub
8889 Private Sub OpenGL_Display ()
90 While True
91 If Now.Subtract(FPSstartTime ). TotalSeconds > 1 Then
92 FPSstartTime = Now
93 UpdateFPSText(CStr(FPSCount ))
94 FPSCount = 0
95 Else
96 FPSCount += 1
97 End If
9899 mView.Refresh ()
100 ’A small slow down. Should really be checked against FPS so as to hold it at a sensible rate
101 Threading.Thread.Sleep (12)
102 End While
103 End Sub
104105 Private Sub cmdShot_Click(ByVal sender As System.Object , ByVal e As System.EventArgs) Handles cmdShot.Click
106 Me.Enabled = False
107 Try
108 Dim bm As Bitmap = mView.takeScreenShot ()
109 If IO.File.Exists("c:\tmp.bmp") Then
110 IO.File.Delete("c:\tmp.bmp")
111 End If
112 bm.Save("c:\tmp.bmp")
113 Catch ex As Exception
114 MsgBox("Error saving snap shot file." & vbCrLf & ex.Message , MsgBoxStyle.OkOnly Or _
115 MsgBoxStyle.Critical , "Error")
116 Finally
117 Me.Enabled = True
118 End Try
119 End Sub
120121 Private Sub cmdLoadPyramid_Click(ByVal sender As System.Object , ByVal e As System.EventArgs) _
122 Handles cmdLoadPyramid.Click
123124 LoadModel("C:\ Documents and Settings\Michael Ball\Desktop\Lego Stuff\Pyramid.dat")
125 End Sub
126127 Private Sub Form1_MouseWheel(ByVal sender As Object , ByVal e As System.Windows.Forms.MouseEventArgs) _
128 Handles Me.MouseWheel
129 ’Pass this event through to the openglviewer
130 mView.OpenGLViewer_MouseWheel(sender , e)
131 End Sub
132133 Private Sub OptionsChanged(ByVal sender As System.Object , ByVal e As System.EventArgs) _
134 Handles chkStuds.CheckedChanged , chkNorms.CheckedChanged , chkBounds.CheckedChanged
135 updateInstructionSet ()
136 End Sub
137138 Private Sub updateInstructionSet ()
139 If Not mView Is Nothing Then
140 mView.InstructionList = ListRenderer.ListModel(mLegoModel , chkStuds.Checked , chkWireframe.Checked , _
141 chkNorms.Checked , chkBounds.Checked , chkOuterShell.Checked)
142 End If
143 End Sub
144145 Private Sub chkWireframe_CheckedChanged(ByVal sender As System.Object , ByVal e As System.EventArgs) _
146 Handles chkWireframe.CheckedChanged
147148 ’Disables this option as it can ’t be deactivated in this mode
149 chkBounds.Enabled = Not (chkWireframe.Checked Or chkOuterShell.Checked)
150 If Not mView Is Nothing Then
151 mView.WireFrame = chkWireframe.Checked
152 updateInstructionSet ()
153 End If
154 End Sub
155156 Private Sub chkOuterShell_CheckedChanged(ByVal sender As System.Object , ByVal e As System.EventArgs) _
157 Handles chkOuterShell.CheckedChanged
158159 chkStuds.Enabled = Not chkOuterShell.Checked ’Disables these options as they can ’t be activated in this mode
160 chkBounds.Enabled = Not (chkOuterShell.Checked Or chkWireframe.Checked)
161 chkShowInterceptions.Enabled = chkOuterShell.Checked
162 chkSmooth.Checked = False
163 chkSmooth.Enabled = chkOuterShell.Checked
164165 If chkOuterShell.Checked Then
166 If Not mLegoModel Is Nothing Then
167 mUnsurfacedLegoModel = mLegoModel
168 mLegoModel = SurfaceFinder.GetOuterSurface(mLegoModel , chkShowInterceptions.Checked ).copy
169 End If
170 ElseIf Not mUnsurfacedLegoModel Is Nothing Then
171 mLegoModel = mUnsurfacedLegoModel
172 End If
173174 updateInstructionSet ()
175 End Sub
176177 Private Sub cmdLoad2Bricks_Click(ByVal sender As System.Object , ByVal e As System.EventArgs) _
178 Handles cmdLoadTestModel.Click
179180 LoadModel("C:\ Documents and Settings\Michael Ball\Desktop \3 bricks.dat")
181 End Sub
61
182183 Private Sub mView_DragDrop(ByVal sender As Object , ByVal e As System.Windows.Forms.DragEventArgs) _
184 Handles mView.DragDrop
185186 ’ Handle FileDrop data.
187 If e.Data.GetDataPresent(DataFormats.FileDrop) Then
188 ’ Assign the file names to a string array , in
189 ’ case the user has selected multiple files.
190 Dim files As String () = CType(e.Data.GetData(DataFormats.FileDrop), String ())
191 Try
192 LoadModel(files (0))
193 Catch ex As Exception
194 MessageBox.Show(ex.Message)
195 Return
196 End Try
197 End If
198199 End Sub
200201 Private Sub mView_DragOver(ByVal sender As Object , ByVal e As System.Windows.Forms.DragEventArgs) _
202 Handles mView.DragOver
203204 e.Effect = DragDropEffects.All
205 End Sub
206207 Private Sub cmdLoad2Bricks_Click_1(ByVal sender As System.Object , ByVal e As System.EventArgs) _
208 Handles cmdLoad2Bricks.Click
209210 LoadModel("C:\ Documents and Settings\Michael Ball\Desktop \2 bricks.dat")
211 End Sub
212213 Private Sub chkShowInterceptions_CheckedChanged(ByVal sender As System.Object , ByVal e As System.EventArgs) _
214 Handles chkShowInterceptions.CheckedChanged
215 mLegoModel = SurfaceFinder.GetOuterSurface(mLegoModel , chkShowInterceptions.Checked)
216 updateInstructionSet ()
217 End Sub
218219 Private Sub chkSmooth_CheckedChanged(ByVal sender As System.Object , ByVal e As System.EventArgs) _
220 Handles chkSmooth.CheckedChanged
221 Dim smoothMethod As eSmoothMethod
222 Select Case True
223 Case optLaplacian.Checked
224 smoothMethod = eSmoothMethod.Laplacian
225 Case optTaubin.Checked
226 smoothMethod = eSmoothMethod.Taubin
227 Case optLoops.Checked
228 smoothMethod = eSmoothMethod.Loop
229 End Select
230231 If chkSmooth.Checked Then
232 If Not mLegoModel Is Nothing Then
233 mUnsmoothedModel = mLegoModel
234 mLegoModel = Smoother.SmoothShape(mLegoModel , smoothMethod , CInt(nudSubdivisions.Value), _
235 CInt(nudSmoothing.Value), Me).copy
236 End If
237 ElseIf Not mUnsurfacedLegoModel Is Nothing Then
238 mLegoModel = mUnsmoothedModel
239 End If
240241 updateInstructionSet ()
242 End Sub
243244 Private Sub SmoothingOptionsChanged_CheckedChanged(ByVal sender As System.Object , ByVal e As System.EventArgs) _
245 Handles optLoops.CheckedChanged , optLaplacian.CheckedChanged , optTaubin.CheckedChanged
246247 chkSmooth.Checked = False
248249 End Sub
250 End Class
62
D.4 ListRenderer.vb
Creates the instruction and value stacks from the internal representation.1 Imports LEGO_Smoothing.ModelDefs
2 Imports LEGO_Smoothing.Common
34 Public Class ListRenderer
56 Private Shared mShowStuds As Boolean
7 Private Shared mWireframe As Boolean
8 Private Shared mShowNorms As Boolean
9 Private Shared mShowBounds As Boolean
10 Private Shared mShowOuterShell As Boolean
1112 Private Shared mInstructions As New ArrayList
13 Private Shared mValues As New ArrayList
1415 Private Shared mCurCol As Single ()
1617 Public Shared Function ListModel(ByVal LM As LegoModel , ByVal ShowStuds As Boolean , _
18 ByVal Wireframe As Boolean , ByVal ShowNorms As Boolean , ByVal showBounds As Boolean , _
19 ByVal showOuterShell As Boolean) As Long
2021 ’This sub just allows defaults to be set , the realDrawModel does the work , and is called recursively
22 mShowStuds = ShowStuds
23 mWireframe = Wireframe
24 mShowNorms = ShowNorms
25 mShowBounds = showBounds
26 mShowOuterShell = showOuterShell
27 mValues.Clear()
28 mInstructions.Clear()
2930 ’Create the initial Identity Matrix
31 RealListModel(LM, False)
3233 mCurCol = Nothing ’Clear mCurCol
3435 Dim reIS As InstructionSet
36 reIS.Instructions = DirectCast(mInstructions.ToArray(GetType(eInstruction )), eInstruction ())
37 reIS.Values = DirectCast(mValues.ToArray(GetType(Single)), Single ())
38 Return Renderer.Render(reIS)
39 End Function
4041 Private Shared Sub RealListModel(ByVal LM As LegoModel , ByVal Inverted As Boolean)
42 If LM IsNot Nothing AndAlso LM.Parts IsNot Nothing Then
43 For Each p As LegoPart In LM.Parts
44 Select Case p.type
45 Case PartType.PartFile
46 DrawExternal(DirectCast(p, Part_External), Inverted)
47 Case PartType.Line
48 DrawLine(DirectCast(p, Part_Line ))
49 Case PartType.Triangle
50 DrawTriangle(DirectCast(p, Part_Triangle), Inverted)
51 Case PartType.Quadrilateral
52 DrawQuad(DirectCast(p, Part_Quadrilateral), Inverted)
53 Case PartType.SpecialLine
54 DrawSpecialLine(DirectCast(p, Part_SpecialLine ))
55 End Select
56 Next
57 End If
58 End Sub
5960 Private Shared Sub DrawExternal(ByVal p As Part_External , ByVal Inverted As Boolean)
61 If p.Stud And (Not mShowStuds) Then ’Or mShowOuterShell ) Then
62 Exit Sub ’Won ’t draw studs
63 End If
6465 RealListModel(p.part , p.inverted Xor Inverted)
66 End Sub
6768 Private Shared Sub DrawLine(ByVal p As Part_Line)
69 ’Wire frame now done by culling front and back of polygons
70 ’so don ’t want to draw the lines aswell
7172 If ((Not mShowBounds) Or mWireframe) And (Not mShowOuterShell) Then Exit Sub
73 AddColour(p.RealColour)
7475 mInstructions.Add(eInstruction.Line)
76 AddPointValues(p.P1)
77 AddPointValues(p.P2)
78 End Sub
7980 Private Shared Sub DrawTriangle(ByVal p As Part_Triangle , ByVal Inverted As Boolean)
81 ’If mWireframe Then Exit Sub ’or mShowOuterShell Then Exit Sub
82 If mShowNorms Then DrawNormal(p.Norm , p)
83 With p
84 AddColour (. RealColour)
8586 mInstructions.Add(eInstruction.Triangle)
87 AddPointValues (.Norm)
88 AddPointValues (.P1)
89 AddPointValues (.P2)
90 AddPointValues (.P3)
63
91 End With
92 End Sub
9394 Private Shared Sub DrawQuad(ByVal p As Part_Quadrilateral , ByVal Inverted As Boolean)
95 If mShowNorms Then DrawNormal(p.Norm , p)
96 ’If mWireframe Then Exit Sub
9798 With p
99 AddColour (. RealColour)
100101 mInstructions.Add(eInstruction.Quadrilateral)
102 AddPointValues (.Norm)
103 AddPointValues (.P1)
104 AddPointValues (.P2)
105 AddPointValues (.P3)
106 AddPointValues (.P4)
107 End With
108 End Sub
109110 Private Shared Sub DrawSpecialLine(ByVal p As Part_SpecialLine)
111 Dim pLine As New Part_Line
112 pLine.colour = p.colour
113 pLine.RealColour = p.RealColour
114 pLine.P1 = p.LineP1
115 pLine.P2 = p.LineP2
116 DrawLine(pLine)
117 End Sub
118119 Private Shared Sub DrawNormal(ByVal Norm As Point3D , ByVal part As LegoPart)
120121 Dim startp As Point3D = Nothing
122123 If TypeOf part Is Part_Quadrilateral Then
124 With DirectCast(part , Part_Quadrilateral)
125 ’startp = Common.SubVector (.P3 , .P1)
126 ’startp = New Point3D (.P3.x - startp.x / 2, .P3.y - startp.y / 2, .P3.z - startp.z / 2)
127 startp = New Point3D ((.P1.x + .P2.x + .P3.x + .P4.x) / 4, (.P1.y + .P2.y + .P3.y + .P4.y) / 4, _
128 (.P1.z + .P2.z + .P3.z + .P4.z) / 4)
129 End With
130 ElseIf TypeOf part Is Part_Triangle Then
131 With DirectCast(part , Part_Triangle)
132 startp = New Point3D ((.P1.x + .P2.x + .P3.x) / 3, (.P1.y + .P2.y + .P3.y) / 3, _
133 (.P1.z + .P2.z + .P3.z) / 3)
134 End With
135 End If
136 Dim endP1 As New Point3D(startp.x + (Norm.x * 4), startp.y + (Norm.y * 4), startp.z + (Norm.z * 4))
137 Dim endP2 As New Point3D(endP1.x + (Norm.x * 2), endP1.y + (Norm.y * 2), endP1.z + (Norm.z * 2))
138 Dim endP3 As New Point3D(startP.x - (Norm.x * 2), startP.y - (Norm.y * 2), startP.z - (Norm.z * 2))
139140 mInstructions.Add(eInstruction.Normal)
141 mValues.Add (0.0F)
142 mValues.Add (0.0F)
143 mValues.Add (1.0F)
144 AddPointValues(startP)
145 AddPointValues(endP1)
146 mValues.Add (1.0F)
147 mValues.Add (0.0F)
148 mValues.Add (0.0F)
149 AddPointValues(endP1)
150 AddPointValues(endP2)
151 mValues.Add (0.0F)
152 mValues.Add (1.0F)
153 mValues.Add (0.0F)
154 AddPointValues(startP)
155 AddPointValues(endP3)
156 mCurCol = New Single () {0.0F, 1.0F, 0.0F}
157 End Sub
158159 Private Shared Sub AddColour(ByVal colour As Single ())
160 ’Check if colour is already correct , if so don ’t add it again , will cut down instruction and value list lengths
161 ’and will mean sending less commands to OPENGL.
162 If Not mCurCol Is Nothing AndAlso colour.Length = mCurCol.Length Then
163 Dim count As Integer = 0
164 For i As Integer = 0 To colour.Length - 1
165 If colour(i) = mCurCol(i) Then
166 count += 1
167 Else
168 ’If one fails then not the same so can exit the loop , as count will never reach
169 ’colour.length anyway
170 Exit For
171 End If
172 Next
173 If count = colour.Length Then
174 Exit Sub
175 End If
176177 End If
178179 mInstructions.Add(eInstruction.Colour)
180 For i As Integer = 0 To colour.Length - 1
181 mValues.Add(colour(i))
182 Next
183 ’Need to pad with 1, i.e. if the alpha isn ’t set , otherwise the render will error as it doesn ’t carry out any
184 ’checks for speed
185
64
186 If colour.Length = 3 Then
187 mValues.Add (1.0F)
188 End If
189190 mCurCol = colour
191 End Sub
192193 Private Shared Sub AddPointValues(ByVal P As Point3D)
194 With P
195 mValues.Add(.x)
196 mValues.Add(.y)
197 mValues.Add(.z)
198 End With
199 End Sub
200201 End Class
65
D.5 ModelDefs.vb
Defines the different objects used within the internal representation (e.g the line,quadrilateral, and triangle parts).
1 Imports LEGO_Smoothing.Common
23 Public Class ModelDefs
4 Public Enum PartType
5 PartFile = 1
6 Line = 2
7 Triangle = 3
8 Quadrilateral = 4
9 SpecialLine = 5
10 End Enum
1112 Public MustInherit Class LegoPart
13 Public type As PartType
14 Public colour As Integer
15 Public RealColour (2) As Single
1617 Public MustOverride Function Copy() As LegoPart
18 End Class
1920 Public Class Part_External
21 Inherits LegoPart
22 Public part As LegoModel
23 Public transformMatrix As Matrix
24 Public Stud As Boolean
25 Public inverted As Boolean = False
26 Public Name As String
2728 Public Sub New()
29 type = PartType.PartFile
30 End Sub
3132 Public Overrides Function Copy() As LegoPart
33 Dim ret As New Part_External
34 ret.colour = Me.colour
35 If Me.RealColour.Length = 3 Then
36 ret.RealColour = New Single () {Me.RealColour (0), Me.RealColour (1), Me.RealColour (2)}
37 Else
38 ret.RealColour = New Single () {Me.RealColour (0), Me.RealColour (1), Me.RealColour (2), Me.RealColour (3)}
39 End If
40 ret.Name = Me.Name
41 ret.inverted = Me.inverted
42 ret.Stud = Me.Stud
43 ret.transformMatrix = Me.transformMatrix
44 ret.part = Me.part.copy
45 Return ret
46 End Function
47 End Class
4849 Public Class Part_Line
50 Inherits LegoPart
51 Public P1 As Point3D
52 Public P2 As Point3D
5354 Public Sub New()
55 type = PartType.Line
56 End Sub
5758 Public Overrides Function Copy() As LegoPart
59 Dim ret As New Part_Line
60 ret.colour = Me.colour
61 If Me.RealColour.Length = 3 Then
62 ret.RealColour = New Single () {Me.RealColour (0), Me.RealColour (1), Me.RealColour (2)}
63 Else
64 ret.RealColour = New Single () {Me.RealColour (0), Me.RealColour (1), Me.RealColour (2), Me.RealColour (3)}
65 End If
66 ret.P1 = Me.P1.Copy
67 ret.P2 = Me.P2.Copy
68 Return ret
69 End Function
70 End Class
7172 Public Class Part_Triangle
73 Inherits LegoPart
74 Public P1 As Point3D
75 Public P2 As Point3D
76 Public P3 As Point3D
77 Public Norm As Point3D
7879 Public Sub New()
80 type = PartType.Triangle
81 End Sub
8283 Public Overrides Function Copy() As LegoPart
84 Dim ret As New Part_Triangle
85 ret.colour = Me.colour
86 If Me.RealColour.Length = 3 Then
87 ret.RealColour = New Single () {Me.RealColour (0), Me.RealColour (1), Me.RealColour (2)}
88 Else
66
89 ret.RealColour = New Single () {Me.RealColour (0), Me.RealColour (1), Me.RealColour (2), Me.RealColour (3)}
90 End If
91 ret.P1 = Me.P1.Copy
92 ret.P2 = Me.P2.Copy
93 ret.P3 = Me.P3.Copy
94 If Me.Norm IsNot Nothing Then ret.Norm = Me.Norm.Copy
95 Return ret
96 End Function
97 End Class
9899 Public Class Part_Quadrilateral
100 Inherits LegoPart
101 Public P1 As Point3D
102 Public P2 As Point3D
103 Public P3 As Point3D
104 Public P4 As Point3D
105 Public Norm As Point3D
106107 Public Sub New()
108 type = PartType.Quadrilateral
109 End Sub
110111 Public Overrides Function Copy() As LegoPart
112 Dim ret As New Part_Quadrilateral
113 ret.colour = Me.colour
114 If Me.RealColour.Length = 3 Then
115 ret.RealColour = New Single () {Me.RealColour (0), Me.RealColour (1), Me.RealColour (2)}
116 Else
117 ret.RealColour = New Single () {Me.RealColour (0), Me.RealColour (1), Me.RealColour (2), Me.RealColour (3)}
118 End If
119 ret.P1 = Me.P1.Copy
120 ret.P2 = Me.P2.Copy
121 ret.P3 = Me.P3.Copy
122 ret.P4 = Me.P4.Copy
123 If Me.Norm IsNot Nothing Then ret.Norm = Me.Norm.Copy
124 Return ret
125 End Function
126 End Class
127128 Public Class Part_SpecialLine
129 Inherits LegoPart
130 Public LineP1 As Point3D
131 Public LineP2 As Point3D
132 Public TestP1 As Point3D
133 Public TestP2 As Point3D
134135 Public Sub New()
136 type = PartType.SpecialLine
137 End Sub
138139 Public Overrides Function Copy() As LegoPart
140 Dim ret As New Part_SpecialLine
141 ret.colour = Me.colour
142 If Me.RealColour.Length = 3 Then
143 ret.RealColour = New Single () {Me.RealColour (0), Me.RealColour (1), Me.RealColour (2)}
144 Else
145 ret.RealColour = New Single () {Me.RealColour (0), Me.RealColour (1), Me.RealColour (2), Me.RealColour (3)}
146 End If
147 ret.LineP1 = Me.LineP1.Copy
148 ret.LineP2 = Me.LineP2.Copy
149 ret.TestP1 = Me.TestP1.Copy
150 ret.TestP2 = Me.TestP2.Copy
151 Return ret
152 End Function
153 End Class
154155 Public Class LegoModels
156 Dim models As Hashtable
157158 Public Sub New()
159 models = New Hashtable
160 End Sub
161162 Public Sub addModel(ByVal Name As String , ByVal LM As LegoModel)
163 models.Add(Name , LM)
164 End Sub
165166 Public Function getModel(ByVal Name As String) As LegoModel
167 ’Clones models
168 If ModelExist(Name) AndAlso Not models(Name) Is Nothing Then
169 Return (DirectCast(models(Name), LegoModel )). copy
170 End If
171 Return Nothing
172 End Function
173174 Public Function ModelExist(ByVal Name As String) As Boolean
175 Return models.ContainsKey(Name)
176 End Function
177 End Class
178179 Public Class LegoModel
180 Public Parts As ArrayList
181182 Public Sub New()
183 Parts = New ArrayList
67
184 End Sub
185186 Public Sub addPart(ByVal LP As LegoPart)
187 Parts.Add(LP)
188 End Sub
189190 Public Function getPart(ByVal index As Integer) As LegoPart
191 Return DirectCast(Parts(index), LegoPart)
192 End Function
193194 Public Function copy() As LegoModel
195 Dim ret As New LegoModel
196 For Each p As LegoPart In Parts
197 ret.addPart(p.Copy)
198 Next
199 Return ret
200 End Function
201 End Class
202203 End Class
68
D.6 OpenGLViewer.vb
The viewer which displays the rendered LEGO object. It extends the control pro-vided within CSGL [8].
1 Imports CsGL.OpenGL
23 <CLSCompliant(False)> _
4 Public Class OpenGLViewer
5 Inherits CsGL.OpenGL.OpenGLControl
67 Private mSizeChecked As Boolean = False
8 Private mMaxModelSize As Single
9 Private mLastMousePos As Point
10 Private mCurrentRot As PointF
11 ’Private mChangeRot As PointF
12 Private mCurrentZ As Integer = -200
1314 Private lightAmbient () As Single = {0.4F, 0.4F, 0.4F, 0.4F}
15 Private lightDiffuse () As Single = {1, 1, 1, 1}
16 Private lightSpecular () As Single = {1, 1, 1, 1}
17 Private lightPosition () As Single = {-10, 10, 2, 1}
1819 Private specReflection () As Single = {0.8F, 0.8F, 0.8F, 1.0F} ’Defines the specular Reflection amount
2021 Private mInstructionList As Long
22 Private mWireFrame As Boolean = False
2324 Delegate Sub refreshCallBack ()
2526 Public Overrides Sub Refresh ()
27 If Me.InvokeRequired Then
28 Dim d As New refreshCallBack(AddressOf Refresh)
29 If Me IsNot Nothing Then Me.Invoke(d, Nothing)
30 Else
31 MyBase.Refresh ()
32 End If
33 End Sub
3435 Public Property InstructionList () As Long
36 Get
37 Return mInstructionList
38 End Get
39 Set(ByVal Value As Long)
40 mInstructionList = Value
41 End Set
42 End Property
4344 Public Property WireFrame () As Boolean
45 Get
46 Return mwireframe
47 End Get
48 Set(ByVal value As Boolean)
49 mwireframe = value
50 If mWireFrame Then
51 GL.glDisable(GL.GL_CULL_FACE)
52 GL.glPolygonMode(GL.GL_FRONT_AND_BACK , GL.GL_LINE)
53 Else
54 GL.glEnable(GL.GL_CULL_FACE)
55 GL.glPolygonMode(GL.GL_FRONT_AND_BACK , GL.GL_FILL)
56 End If
57 End Set
58 End Property
5960 Public Overrides Sub glDraw ()
61 GL.glClearColor (0.0, 0.0, 0.0, 0.0) ’Black
62 ’GL. glClearColor (1, 1, 1, 0) ’White
63 GL.glClear(Convert.ToUInt32(GL.GL_COLOR_BUFFER_BIT Or GL.GL_DEPTH_BUFFER_BIT ))
64 GL.glLoadIdentity ()
6566 GL.glTranslatef (0, 0, mCurrentZ)
67 GL.glRotatef (180, 1.0F, 0.0F, 0.0F)
68 GL.glRotatef(mCurrentRot.Y, 1.0F, 0.0F, 0.0F)
69 GL.glRotatef(mCurrentRot.X, 0.0F, 1.0F, 0.0F)
7071 If mInstructionList <> 0 Then
72 GL.glCallList(CUInt(mInstructionList ))
73 End If
74 GL.glFlush ()
7576 End Sub
7778 Protected Overrides Sub InitGLContext ()
7980 GL.glBlendFunc(GL.GL_SRC_ALPHA , GL.GL_ONE_MINUS_SRC_ALPHA)
81 GL.glEnable(GL.GL_BLEND)
8283 GL.glCullFace(GL.GL_BACK)
84 GL.glEnable(GL.GL_CULL_FACE)
8586 GL.glEnable(GL.GL_LIGHTING)
87 GL.glEnable(GL.GL_COLOR_MATERIAL)
88 GL.glShadeModel(GL.GL_SMOOTH) ’ Enable Smooth Shading
69
89 GL.glClearDepth (1) ’ Depth Buffer Setup
90 GL.glEnable(GL.GL_DEPTH_TEST) ’ Enables Depth Testing
91 GL.glDepthFunc(GL.GL_LEQUAL) ’ The Type Of Depth Testing To Do
92 GL.glHint(GL.GL_PERSPECTIVE_CORRECTION_HINT , GL.GL_NICEST) ’ Really Nice Perspective Calculations
93 GL.glLightfv(GL.GL_LIGHT1 , GL.GL_AMBIENT , lightAmbient) ’ Setup The Ambient Light
94 GL.glLightfv(GL.GL_LIGHT1 , GL.GL_DIFFUSE , lightDiffuse) ’ Setup The Diffuse Light
95 GL.glLightfv(GL.GL_LIGHT1 , GL.GL_SPECULAR , lightSpecular) ’ Setup The Specular Light
96 GL.glLightfv(GL.GL_LIGHT1 , GL.GL_POSITION , lightPosition) ’ Position The Light
97 GL.glEnable(GL.GL_LIGHT1)
98 OpenGLViewer_Resize(Nothing , Nothing)
99100 ’All bricks will have the same shininess property , so can be set once here.
101 GL.glMaterialfv(GL.GL_FRONT , GL.GL_SPECULAR , specReflection)
102 GL.glMateriali(GL.GL_FRONT , GL.GL_SHININESS , 96)
103 End Sub
104105 Private fRotate As Single = 0.0F
106 Protected Sub myView_OnKeyDown(ByVal Sender As Object , ByVal kea As System.Windows.Forms.KeyEventArgs) _
107 Handles MyBase.KeyDown
108109 Select Case kea.KeyData
110 Case Keys.Escape
111 Application.Exit()
112 End Select
113 End Sub
114115 Public Function takeScreenShot () As Bitmap
116 Try
117 Dim mSize As Integer = 3 * Width * Height
118 Dim bm As New Bitmap(Width , Height)
119 Dim pixels(mSize) As Byte
120 GL.glReadPixels (0, 0, Width , Height , Convert.ToUInt32(GL.GL_RGB), _
121 Convert.ToUInt32(GL.GL_UNSIGNED_BYTE), pixels)
122123 Dim index As Integer = 0
124 While index < mSize
125 Dim ly As Integer = CInt(Int(( index / 3) / Width ))
126 Dim lx As Integer = CInt(index / 3) - (ly * Width)
127 bm.SetPixel(lx, Height - ly - 1, Color.FromArgb(Convert.ToInt32(pixels(index)), _
128 Convert.ToInt32(pixels(index + 1)), Convert.ToInt32(pixels(index + 2))))
129130 index += 3
131 End While
132 Return bm
133 Catch ex As Exception
134 MsgBox("Error trying to take snap shot." & vbCrLf & ex.Message , MsgBoxStyle.OkOnly Or _
135 MsgBoxStyle.Critical , "Error")
136137 Return Nothing
138 End Try
139 End Function
140141 Private Sub OpenGLViewer_MouseDown(ByVal sender As Object , ByVal e As System.Windows.Forms.MouseEventArgs) _
142 Handles Me.MouseDown
143144 If e.Button = Windows.Forms.MouseButtons.Left Then
145 mLastMousePos = e.Location
146 End If
147 End Sub
148149 Private Sub OpenGLViewer_MouseMove(ByVal sender As Object , ByVal e As System.Windows.Forms.MouseEventArgs) _
150 Handles Me.MouseMove
151152 If MouseButtons = Windows.Forms.MouseButtons.Left AndAlso mLastMousePos.X >= 0 Then
153 mCurrentRot = New PointF(mCurrentRot.X + (mLastMousePos.X - e.X), mCurrentRot.Y - (mLastMousePos.Y - e.Y))
154 mLastMousePos = e.Location
155 End If
156 End Sub
157158 Public Sub OpenGLViewer_MouseWheel(ByVal sender As Object , ByVal e As System.Windows.Forms.MouseEventArgs) _
159 Handles Me.MouseWheel
160161 ’One "turn" of the mouse wheel is e.Delta = 120. So dividing by 12 means each "turn" changes the z by 10
162 mCurrentZ += CInt(e.Delta / 12)
163 End Sub
164165 Private Sub OpenGLViewer_Resize(ByVal sender As Object , ByVal e As System.EventArgs) Handles Me.Resize
166 If Me.Height = 0 Then Height = 1 ’ Prevent a divide by zero by making height equal one
167 GL.glViewport (0, 0, Me.Width , Me.Height) ’Reset the current viewport
168 GL.glMatrixMode(GL.GL_PROJECTION) ’Select the projection matrix
169 GL.glLoadIdentity () ’Reset the project matrix
170 GL.gluPerspective (45.0F, Me.Width / Me.Height , 0.1F, 1000.0F) ’Calculate the aspect ration of the window
171 GL.glMatrixMode(GL.GL_MODELVIEW) ’ Select the modelview matrix
172 GL.glLoadIdentity ()
173 End Sub
174 End Class
70
D.7 Renderer.vb
Converts the instruction and value stacks into OpenGL instructions and rendersthem to the OpenGL internal list.
1 Imports LEGO_Smoothing.Common
2 Imports CsGL.OpenGL
34 Public Class Renderer
56 Private Shared mValues () As Single
7 Private Shared mValPtr As Integer
89 Public Shared Function Render(ByVal Instructions As InstructionSet) As Long
10 Dim list As Long = GL.glGenLists (1)
11 GL.glNewList(CUInt(list), GL.GL_COMPILE)
1213 GL.glLineWidth (2.0F)
14 mValues = Instructions.Values
15 Dim ins() As eInstruction = Instructions.Instructions
16 mValPtr = 0
1718 For i As Integer = 0 To ins.Length - 1
19 Select Case ins(i)
20 Case eInstruction.Line
21 GL.glBegin(GL.GL_LINES)
22 DrawVertex ()
23 DrawVertex ()
24 GL.glEnd()
2526 Case eInstruction.Quadrilateral
27 GL.glBegin(GL.GL_QUADS)
28 SetNormal ()
29 DrawVertex ()
30 DrawVertex ()
31 DrawVertex ()
32 DrawVertex ()
33 GL.glEnd()
3435 Case eInstruction.Triangle
36 GL.glBegin(GL.GL_TRIANGLES)
37 SetNormal ()
38 DrawVertex ()
39 DrawVertex ()
40 DrawVertex ()
41 GL.glEnd()
4243 Case eInstruction.Normal
44 GL.glBegin(GL.GL_LINES)
45 SetColour3 ()
46 DrawVertex ()
47 DrawVertex ()
48 SetColour3 ()
49 DrawVertex ()
50 DrawVertex ()
51 SetColour3 ()
52 DrawVertex ()
53 DrawVertex ()
54 GL.glEnd()
5556 Case eInstruction.Colour
57 SetColour4 ()
58 End Select
59 Next
6061 GL.glEndList ()
62 Return list
63 End Function
6465 Private Shared Sub DrawVertex ()
66 GL.glVertex3f(mValues(mValPtr), mValues(mValPtr + 1), mValues(mValPtr + 2))
67 mValPtr += 3
68 End Sub
6970 Private Shared Sub SetNormal ()
71 GL.glNormal3f(mValues(mValPtr), mValues(mValPtr + 1), mValues(mValPtr + 2))
72 mValPtr += 3
73 End Sub
7475 Private Shared Sub SetColour3 ()
76 GL.glColor3f(mValues(mValPtr), mValues(mValPtr + 1), mValues(mValPtr + 2))
77 mValPtr += 3
78 End Sub
7980 Private Shared Sub SetColour4 ()
81 GL.glColor4f(mValues(mValPtr), mValues(mValPtr + 1), mValues(mValPtr + 2), mValues(mValPtr + 3))
82 mValPtr += 4
83 End Sub
8485 End Class
71
D.8 Smoother.vb
Provides the smoothing and subdivision code, and is the file which can be changedto include more or different smoothing methods.
1 Imports LEGO_Smoothing.Common
2 Imports LEGO_Smoothing.ModelDefs
34 Public Class Smoother
56 Const lambda As Single = 0.4 ’0.6307 ’0.4
7 Const Kpb As Single = 0.01
8 Const mu As Single = 1.0 / (Kpb - (1.0 / lambda ))
910 Const PassNum As Integer = 1
11 Private Shared SmoothIterations As Integer = 100
12 Private Shared SubdivideIterations As Integer = 3
1314 Private Shared HashFixedPoints As New Hashtable
1516 ’Private Shared fixedPoints As New ArrayList
17 Private Shared newPoints As New ArrayList
1819 Private Shared Vertices As New Hashtable
2021 Private Shared frmSmoothProg As frmSmoothProgress
2223 Public Shared Function SmoothShape(ByVal LM As LegoModel , ByVal Method As eSmoothMethod , _
24 ByVal Subdivisons As Integer , ByVal Smoothing As Integer , Optional ByVal Parent As Form = Nothing) As LegoModel
25 SmoothIterations = Smoothing
26 SubdivideIterations = Subdivisons
272829 Vertices.Clear()
3031 Dim smoothedModel As LegoModel = LM.copy
32 ’fixedPoints .Clear ()
33 HashFixedPoints.Clear ()
3435 smoothedModel = CutIntoTriangles(smoothedModel)
3637 ’This creates smaller and more regualarly shape triangles which produce a better effect for smoothing
38 ’smoothedModel = subdivide( smoothedModel )
39 ’MergeVertices ( smoothedModel )
40 ’addTrianglesToHashTab ( smoothedModel )
4142 frmSmoothProg = New frmSmoothProgress
43 frmSmoothProg.Show(Parent)
44 frmSmoothProg.TopMost = True
45 frmSmoothProg.PassTotal = 0
46 Application.DoEvents ()
4748 Select Case Method
49 Case eSmoothMethod.Loop
50 LoopControl(smoothedModel)
51 Case eSmoothMethod.Taubin
52 TaubinControl(smoothedModel)
53 Case eSmoothMethod.Laplacian
54 LaplacianControl(smoothedModel)
55 End Select
5657 frmSmoothProg.Process = "Calculating Normals"
5859 ’Recalculate the normals of each shape in the mesh so lighting looks correct
60 For Each s As LegoPart In smoothedModel.Parts
61 Select Case s.type
62 Case PartType.Quadrilateral
63 With DirectCast(s, Part_Quadrilateral)
64 .Norm = Common.CalcNormal(New Point3D () {.P1, .P2 , .P3, .P4})
65 End With
66 Case PartType.Triangle
67 With DirectCast(s, Part_Triangle)
68 .Norm = Common.CalcNormal(New Point3D () {.P1, .P2 , .P3})
69 End With
70 End Select
71 frmSmoothProg.Percent += (0.25 / smoothedModel.Parts.Count)
72 Next
7374 frmSmoothProg.Close()
7576 Return smoothedModel
77 End Function
7879 Private Shared Sub LaplacianControl(ByRef LM As LegoModel)
80 frmSmoothProg.Process = "Subdividing"
81 For subdivideindex As Integer = 1 To SubdivideIterations
82 LM = subdivide(LM)
83 frmSmoothProg.Percent += (0.25 / SubdivideIterations)
84 Next
8586 MergeVertices(LM, 0.25)
87 addTrianglesToHashTab(LM)
88
72
89 frmSmoothProg.Process = "Smoothing"
90 For smoothIndex As Integer = 1 To SmoothIterations
91 LaplacianSmooth ()
92 frmSmoothProg.Percent += (0.25 / SmoothIterations)
93 Next smoothIndex
94 End Sub
9596 Private Shared Sub LoopControl(ByRef LM As LegoModel)
97 For subdivideIndex As Integer = 1 To SubdivideIterations
98 frmSmoothProg.Process = "Subdividing"
99 LM = subdivide(LM)
100101 MergeVertices(LM, CSng (0.25 / SubdivideIterations ))
102103 addTrianglesToHashTab(LM)
104105 frmSmoothProg.Process = "Smoothing"
106 LoopsMask ()
107 frmSmoothProg.Percent += (0.5 / SubdivideIterations)
108 Next subdivideIndex
109110 End Sub
111112 Private Shared Sub TaubinControl(ByRef LM As LegoModel)
113 frmSmoothProg.Process = "Subdividing"
114 For subdivideIndex As Integer = 1 To SubdivideIterations
115 LM = subdivide(LM)
116 frmSmoothProg.Percent += (0.25 / SubdivideIterations)
117 Next subdivideIndex
118119 MergeVertices(LM, 0.25)
120 addTrianglesToHashTab(LM)
121122 ’’Smooth
123124 frmSmoothProg.Process = "Smoothing"
125 For smoothIndex As Integer = 1 To SmoothIterations
126 TaubinSmoothModel(lambda)
127 TaubinSmoothModel(mu)
128 frmSmoothProg.Percent += (0.25 / SmoothIterations)
129 Next smoothIndex
130 End Sub
131132 Private Shared Sub addTrianglesToHashTab(ByVal LM As LegoModel)
133 Vertices.Clear()
134 For Each p As LegoPart In LM.Parts
135 If TypeOf p Is Part_Triangle Then
136 AddTriangleToHash(DirectCast(p, Part_Triangle ))
137 End If
138 Next
139 End Sub
140141 Private Shared Function subdivide(ByVal LM As LegoModel) As LegoModel
142 ’Should only be called after CutIntoTriangles , as only subdivides triangles
143144 ’Uses Loops Subdivision method which is based on the umbrella subdivison method.
145 ’Will cut each triangle into 4 new ones , by spliting the original edges in their centres , and then connecting
146 ’all points up to create the 4 new triangles .
147148 ’Only the subdivision is carried out here. Loops Masking is implemented in LoopsMask
149150 Dim ret As New LegoModel
151152 For Each p As Part_Triangle In LM.Parts
153 With p
154 Dim newPoint1 As Point3D = AddVector(ScaleVector(SubVector (.P2 , .P1), 0.5), .P1)
155 Dim newPoint2 As Point3D = AddVector(ScaleVector(SubVector (.P3 , .P1), 0.5), .P1)
156 Dim newPoint3 As Point3D = AddVector(ScaleVector(SubVector (.P3 , .P2), 0.5), .P2)
157158 Dim newT1 As New Part_Triangle
159 newT1.RealColour = .RealColour
160 newT1.colour = .colour
161 newT1.Norm = .Norm
162 newT1.P1 = .P1
163 newT1.P2 = newPoint1
164 newT1.P3 = newPoint2
165166 Dim newT2 As New Part_Triangle
167 newT2.RealColour = .RealColour
168 newT2.colour = .colour
169 newT2.Norm = .Norm
170 newT2.P1 = .P2
171 newT2.P2 = newPoint3
172 newT2.P3 = newPoint1
173174 Dim newT3 As New Part_Triangle
175 newT3.RealColour = .RealColour
176 newT3.colour = .colour
177 newT3.Norm = .Norm
178 newT3.P1 = .P3
179 newT3.P2 = newPoint2
180 newT3.P3 = newPoint3
181182 Dim newT4 As New Part_Triangle
183 newT4.RealColour = .RealColour
73
184 newT4.colour = .colour
185 newT4.Norm = .Norm
186 newT4.P1 = newPoint1
187 newT4.P2 = newPoint3
188 newT4.P3 = newPoint2
189190 ret.addPart(newT1)
191 ret.addPart(newT2)
192 ret.addPart(newT3)
193 ret.addPart(newT4)
194 End With
195 Next
196 Return ret
197 End Function
198199 Private Shared Sub LoopsMask ()
200 ’Loop ’s Mask new vertex positioning
201 Dim alpha , Beta As Double
202 Dim neighbourhood As ArrayList
203 Dim neighbourhoodSize As Integer
204 newPoints.Clear()
205 Dim v As Point3D
206207 For Each p As Point3D In HashFixedPoints.Values
208 v = New Point3D
209 neighbourhood = DirectCast(Vertices(p), ArrayList)
210 neighbourhoodSize = neighbourhood.Count
211 Beta = (5 / 4) - (((3 + 2 * Math.Cos((2 * Math.PI) / neighbourhoodSize )) ^ 2) / 32)
212 alpha = (neighbourhoodSize * (1 - Beta)) / Beta
213214 For Each neighbour As Point3D In neighbourhood
215 v = AddVector(v, neighbour)
216 Next
217218 v = AddVector(ScaleVector(p, CSng(alpha)), v)
219 v = ScaleVector(v, CSng(1 / (alpha + neighbourhoodSize )))
220 newPoints.Add(v)
221 Next
222223 Dim i As Integer = 0
224 For Each p As Point3D In HashFixedPoints.Values
225 p.x = DirectCast(newPoints(i), Point3D ).x
226 p.y = DirectCast(newPoints(i), Point3D ).y
227 p.z = DirectCast(newPoints(i), Point3D ).z
228 i += 1
229 Next
230231 End Sub
232233 Private Shared Sub TaubinSmoothModel(ByVal constant As Single)
234 Dim weight As Double
235 Dim dx As Point3D
236 Dim neighbourhood As ArrayList
237 newPoints.Clear()
238239 For Each p As Point3D In HashFixedPoints.Values
240 dx = New Point3D
241 neighbourhood = DirectCast(Vertices(p), ArrayList)
242 weight = 1 / neighbourhood.Count
243244 For Each neighbour As Point3D In neighbourhood
245 dx = AddVector(dx, ScaleVector(SubVector(neighbour , p), CSng(weight )))
246 Next
247 newPoints.Add(AddVector(p, ScaleVector(dx, constant )))
248 Next
249250 Dim i As Integer = 0
251 For Each p As Point3D In HashFixedPoints.Values
252 p.x = DirectCast(newPoints(i), Point3D ).x
253 p.y = DirectCast(newPoints(i), Point3D ).y
254 p.z = DirectCast(newPoints(i), Point3D ).z
255 i += 1
256 Next
257258 End Sub
259260 Private Shared Sub LaplacianSmooth ()
261 Dim dx As Point3D
262 Dim neighbourhood As ArrayList
263 newPoints.Clear()
264265 dx = New Point3D
266 For Each p As Point3D In HashFixedPoints.Values
267 dx.clear()
268 neighbourhood = DirectCast(Vertices(p), ArrayList)
269270 For Each neighbour As Point3D In neighbourhood
271 dx = AddVector(dx, neighbour)
272 Next
273 newPoints.Add(ScaleVector(dx , CSng(1 / neighbourhood.Count )))
274 Next
275276 Dim i As Integer = 0
277 For Each p As Point3D In HashFixedPoints.Values
278 p.x = DirectCast(newPoints(i), Point3D ).x
74
279 p.y = DirectCast(newPoints(i), Point3D ).y
280 p.z = DirectCast(newPoints(i), Point3D ).z
281 i += 1
282 Next
283 End Sub
284285 Private Shared Sub LaplacianSmoothNonShrink(ByVal constant As Single)
286 Dim dx As Point3D
287 Dim neighbourhood As ArrayList
288 newPoints.Clear()
289290 For Each p As Point3D In HashFixedPoints.Values
291 dx = New Point3D
292 neighbourhood = DirectCast(Vertices(p), ArrayList)
293294 For Each neighbour As Point3D In neighbourhood
295 dx = AddVector(dx, ScaleVector(neighbour , constant ))
296 Next
297 newPoints.Add(ScaleVector(dx , CSng(1 / neighbourhood.Count )))
298 Next
299300 Dim i As Integer = 0
301 For Each p As Point3D In HashFixedPoints.Values
302 p.x = DirectCast(newPoints(i), Point3D ).x
303 p.y = DirectCast(newPoints(i), Point3D ).y
304 p.z = DirectCast(newPoints(i), Point3D ).z
305 i += 1
306 Next
307 End Sub
308309 Private Shared Function CutIntoTriangles(ByVal LM As LegoModel) As LegoModel
310 ’Just cuts quads into triangles by splitting them in half
311 Dim t1, t2 As Part_Triangle
312 Dim triangles ()() As Point3D
313 Dim ret As New LegoModel
314 Dim q As Part_Quadrilateral
315316 For Each p As LegoPart In LM.Parts
317 If TypeOf p Is Part_Quadrilateral Then
318 q = DirectCast(p, Part_Quadrilateral)
319320 t1 = New Part_Triangle
321 t2 = New Part_Triangle
322323 t1.Norm = q.Norm
324 t1.RealColour = q.RealColour
325 t1.colour = q.colour
326 t2.Norm = q.Norm
327 t2.RealColour = q.RealColour
328 t2.colour = q.colour
329330 triangles = Common.MakeTriangles(q)
331 t1.P1 = triangles (0)(0)
332 t1.P2 = triangles (0)(1)
333 t1.P3 = triangles (0)(2)
334335 t2.P1 = triangles (1)(0)
336 t2.P2 = triangles (1)(1)
337 t2.P3 = triangles (1)(2)
338339 ret.addPart(t1)
340 ret.addPart(t2)
341 ElseIf TypeOf p Is Part_Triangle Then
342343 ret.addPart(p)
344 End If
345346 Next
347 Return ret
348 End Function
349350 Private Shared Sub AddTriangleToHash(ByVal T As Part_Triangle)
351 Dim arr As ArrayList
352 Dim contained As Boolean
353 Dim points () As Point3D = New Point3D () {T.P1, T.P2, T.P3}
354355 For i As Integer = 0 To 2
356 If Vertices.ContainsKey(points(i)) Then
357 arr = DirectCast(Vertices(points(i)), ArrayList)
358 contained = True
359 Else
360 arr = New ArrayList
361 contained = False
362 End If
363364 Select Case i
365 Case 0
366 arr.AddRange(New Point3D () {T.P2, T.P3})
367 Case 1
368 arr.AddRange(New Point3D () {T.P1, T.P3})
369 Case 2
370 arr.AddRange(New Point3D () {T.P1, T.P2})
371 End Select
372 RemoveDuplicates(arr)
373
75
374 If Not contained Then
375 Vertices.Add(points(i), arr)
376 End If
377 Next
378 End Sub
379380 Private Shared Sub MergeVertices(ByVal LM As LegoModel , ByVal PercentageValue As Single)
381 ’Percentage Value is the percentage of this process compared to the whole smoothing method
382383 frmSmoothProg.Process = "Merging Vertices"
384 For Each t As Part_Triangle In LM.Parts
385 PointInFixedPoints(t.P1)
386 PointInFixedPoints(t.P2)
387 PointInFixedPoints(t.P3)
388 frmSmoothProg.Percent += (PercentageValue / LM.Parts.Count)
389 Next
390 End Sub
391392 Structure PointTest
393 Dim x As Single
394 Dim y As Single
395 Dim z As Single
396 End Structure
397398 Private Shared Sub PointInFixedPoints(ByRef P As Point3D)
399 Dim pt As PointTest
400 pt.x = P.x
401 pt.y = P.y
402 pt.z = P.z
403404 If HashFixedPoints.ContainsKey(pt) Then
405 P = DirectCast(HashFixedPoints.Item(pt), Point3D)
406 Else
407 HashFixedPoints.Add(pt, P)
408 End If
409 End Sub
410411 End Class
76
D.9 SurfaceFinder.vb
Carries out the overlap resolving and when completed would return a mesh holdingjust the outer surface.
1 Imports LEGO_Smoothing.Common
2 Imports LEGO_Smoothing.ModelDefs
34 Public Class SurfaceFinder
56 Private Shared mShowInterceptions As Boolean
78 Private Class Shape
9 Public Part As Part_Quadrilateral
10 Public ExterierVertices As New ArrayList
11 Public IntersectionVertices As New ArrayList
12 Public InternalVertices As New ArrayList
13 Public LineIntersections As New ArrayList
14 Public ValidVertexPairs As New ArrayList
15 End Class
1617 Private Shared Function NoSurfaceFind(ByVal LM As LegoModel) As LegoModel
18 Dim quickRet As New LegoModel
1920 For Each p As LegoPart In LM.Parts
21 If TypeOf p Is Part_Quadrilateral Then
22 quickRet.addPart(p)
23 ElseIf TypeOf p Is Part_External AndAlso Not DirectCast(p, Part_External ).Name.Contains("stud") Then
24 For Each p2 As LegoPart In NoSurfaceFind(DirectCast(p, Part_External ).part).parts
25 quickRet.addPart(p2)
26 Next
27 End If
28 Next
29 Return quickRet
30 End Function
313233 Public Shared Function GetOuterSurface(ByVal LM As LegoModel , ByVal ShowInterceptions As Boolean) As LegoModel
34 ’Return NoSurfaceFind (LM)
353637 mShowInterceptions = ShowInterceptions
38 ’Remove all shapes but quads. Triangles could be kept , but will not be for the simple case as they
39 ’are not present in simple lego bricks
4041 ’Dim tmpQuads As New ArrayList ’An array list is used as this is simple to add an unknown number of items to ,
42 ’a typed array would have to be ’redimed ’ each time round as the length is unknown
4344 Dim Quads As New ArrayList ’Part_Quadrilateral ’After the list length is known , i.e. tmpQuads is populated
45 ’it is converted to a typed array , to save every operation having to type cast each item
4647 ’Main is the prefix for the part actually being tested
48 Dim mainIndex As Integer = 0
49 Dim mainPart As Part_Quadrilateral
5051 ’Check is the prefix for the parts the main part is being checked against
52 Dim checkIndex As Integer = 0
53 Dim checkPart As Part_Quadrilateral = Nothing
5455 ’Populate tmpQuads
56 ’tmpQuads = GetQuads(LM.Parts)
57 Quads = GetQuads(LM.Parts)
5859 ’Convert to typed array
60 ’Quads = DirectCast (tmpQuads.ToArray(GetType( Part_Quadrilateral )), Part_Quadrilateral ())
6162 ’See documentation for better description of this part
6364 ’1 - Remove overlapping quads
6566 ’1.1 - Find intersections and remove quads/vertex based only on vertices
6768 ’1.1.1 - If all 4 vertices are the same then remove
69 Check1_1_1(DirectCast(Quads.ToArray(GetType(Part_Quadrilateral )), Part_Quadrilateral ()))
70 ’1.1.2 - Find intersecting vertices
7172 Dim ret As New LegoModel
73 Dim shp1 , shp2 As Shape
7475 While mainIndex < Quads.Count
76 mainPart = DirectCast(Quads(mainIndex), Part_Quadrilateral)
77 checkIndex = mainIndex + 1
78 While checkIndex < Quads.Count
79 ’checkPart = DirectCast (Quads( checkIndex ), Part_Quadrilateral )
8081 ’If the two quads aren ’t in the same direction then they don ’t overlap in any way , and
82 ’therefore don ’t need to be removed and therefore tested against.
83 ’In this case the same direction means either the same normal or the inverted normal
84 While checkIndex < Quads.Count
85 checkPart = DirectCast(Quads(checkIndex), Part_Quadrilateral)
86 If mainPart.Norm.Equals(checkPart.Norm) OrElse _
87 mainPart.Norm.Equals(New Point3D(-checkPart.Norm.x, -checkPart.Norm.y, -checkPart.Norm.z)) Then
88 ’The same direction , so can proceed with testing
77
89 Exit While
90 End If
91 checkIndex += 1
92 End While
93 ’If it got to the end of the check parts , then should skip to the next main part
94 If checkIndex = Quads.Count Then Exit While
9596 ’1.1.2 - Find if mainpart vertices are ’inside , intercepting , or outside the check quad
97 shp1 = IntersectVertexCheck1_1_2(mainPart , checkPart , True)
9899 ’1.1.2 - Find if check quad vertices are inside , intercepting , or outside the mainpart
100 shp2 = IntersectVertexCheck1_1_2(checkPart , mainPart , True)
101102 ’Create an array of pairs specifying how the different intersections and vertices
103 ’can be correctly joined together
104 CalculateValidVertexPairs(shp1 , shp2)
105106 If shp1.ExterierVertices.Count = 0 And shp2.ExterierVertices.Count = 0 Then
107 ’1.1.4 - All vertices of both shapes intercept , both can be removed
108 Quads.Remove(mainPart)
109 Quads.Remove(checkPart)
110 mainIndex -= 1
111 Exit While
112 End If
113114 If mShowInterceptions Then
115 DisplayIntersections(shp1 , shp2 , ret)
116 End If
117118 ’1.2.1 - THIS HAS BEEN REMOVED AS WASN ’T VALID FOR ALL CASES. See documentation for more information .
119 ’1.2.1 - No line intersections and only a max or two intersecting vertices then shape is just touching
120 ’the other shape , so can move onto next as nothing to remove
121122 If shp1.InternalVertices.Count = 0 AndAlso shp2.InternalVertices.Count = 0 _
123 AndAlso (shp1.LineIntersections.Count > 0 Or shp2.LineIntersections.Count > 0) Then
124125 ’1.2.2a - No internal points , so join valid intersections , line intersections and external vertices
126 ’Used to use its on method , but this is the same as now used in MakeQuads122c so re -uses code
127 If MakeQuads122c(shp1 , shp2 , Quads , mainIndex , checkIndex) Then
128 ’As the original quads will have been removed need to deduct 1 from the mainIndex
129 Exit While ’No point checking any more quads against this main part as it no longer exists
130 End If
131 ElseIf shp1.InternalVertices.Count = 0 AndAlso shp2.InternalVertices.Count = 0 _
132 AndAlso shp1.LineIntersections.Count = 0 AndAlso shp1.IntersectionVertices.Count > 1 Then
133134 ’1.2.2.b - No internal points , or line intersections , but has got vertex intersections , so
135 ’could be overlapping shapes or could just be touching shapes
136137 ’Test if the validPairs created earlier define a complete path , i.e. get from start to start
138 ’going via all the other points. If so , can just keep this quad as is. If one is ok then
139 ’both are ok. So only need to test once for both quads.
140141 ’Shape 1 has no exterier vertices either , so it is covered completly by shape 2, so can
142 ’be removed , but needs to be used by shape 2 to create the correct remaining shape , this
143 ’is then swapped to test the case of shape 2 having no exterier vertices
144 Dim CanRemoveShape1 As Boolean = (shp1.ExterierVertices.Count = 0)
145 Dim CanRemoveShape2 As Boolean = (shp2.ExterierVertices.Count = 0)
146147 ’If all shape 1 interception points are also its vertex points then it doesn ’t need to
148 ’be tested against the other shape , as it will remain the same shape.
149 ’The other shape may still need to be checked though
150151 ’Exterier vertices are kept , and now just find if an exterier can still connected to
152 ’another exterier. If not it now connects to the closest interception of the other shape.
153154 If (Not CanRemoveShape2) Then
155 If MakeAndAddQuadsToQuadList(DirectCast(shp2.ValidVertexPairs.ToArray(GetType(Object ())), _
156 Object ()), mainPart , checkPart , Quads , False) Then
157158 checkIndex -= 1
159 End If
160 Else
161 Quads.Remove(checkPart)
162 checkIndex -= 1
163 End If
164165 If Not CanRemoveShape1 Then
166 If MakeAndAddQuadsToQuadList(DirectCast(shp1.ValidVertexPairs.ToArray(GetType(Object ())), _
167 Object ()), checkPart , mainPart , Quads , False) Then
168169 mainIndex -= 1
170 Exit While
171 End If
172 Else
173 Quads.Remove(mainPart)
174 mainIndex -= 1
175 Exit While
176 End If
177 Else
178 ’1.2.2c - Handle Internal intersections , by joining all known vertices as in parts 1.2.2a
179 ’and 1.2.2b, and then connect the ends to the internal intersections . Which will complete
180 ’the shape. Then split this shape to give 4 vertex shapes , i.e. quads
181182 ’1.1.5 - Swap internal vertices between the two shapes , as these are internal they are no longer
183 ’needed by the original shape , but are now part of the other shape
78
184 ’(as its being cut by the original shape)
185186 ’## Only needs doing when looking for internal vertices
187 ’## causes 1.2.2b to fail otherwise
188 If shp1.InternalVertices.Count > 0 Or shp2.InternalVertices.Count > 0 Then
189 Dim tmpInternals As ArrayList = shp1.InternalVertices
190 shp1.InternalVertices = shp2.InternalVertices
191 shp2.InternalVertices = tmpInternals
192 End If
193194 If MakeQuads122c(shp1 , shp2 , Quads , mainIndex , checkIndex) Then
195 ’Check if MakeQuads122c has removed the main part
196 Exit While
197 End If
198 End If
199 checkIndex += 1
200 End While
201 mainIndex += 1
202 End While
203204 For Each quad As Part_Quadrilateral In Quads
205 ’Add the remaining quads to the returning lego model , these should only be the quads which make up
206 ’the outside surface of the model
207 ret.addPart(quad)
208 Next
209210 Return ret
211 End Function
212213 Private Shared Function MakeQuads122c(ByVal MainShape As Shape , ByVal CheckShape As Shape , _
214 ByRef QuadList As ArrayList , ByRef MainIndex As Integer , ByRef CheckIndex As Integer) As Boolean
215 ’ ’’The valid vertex pairs found for 1.2.2a which are generated while finding the different
216 ’ ’’interceptions , and are therefore available to all the methods. Do not work correctly for internal
217 ’ ’’vertices. While this could probably be changed in the initial finding for 1.2.2a it could
218 ’ ’’also stop the other methods working. Therefore correct valid vertex pairs for 1.2.2c will be
219 ’ ’’calculated here.
220221 ’ ’’CalculateValidVertexPairs122c (MainShape , CheckShape )
222223 ’ ’’Join externals , but need to check there are no intersecion points in the way.
224 ’ ’’If an external is an interception point then join to it , but don ’t go beyond that external.
225 ’ ’’If there is an interception then make a valid pair to it , and then from it to the next external.
226 ’ ’’If line interceptions should already be known. Join the external to the line interception , and again
227 ’ ’’go no further.
228229 Dim shapes () As Shape = New Shape() {CheckShape , MainShape}
230 Dim validPairs As New ArrayList
231 Dim ret As Boolean = False
232 Dim splitValidVertexPairs As New ArrayList
233234 For i As Integer = 0 To 1
235 validPairs.Clear ()
236237 ’((i - 1) * -1) - Swaps 0 to 1 and 1 to 0. So is able to select the other shape.
238 Dim otherShape As Shape = shapes ((i - 1) * -1)
239240 With shapes(i)
241 If .ExterierVertices.Count = 0 Then
242 ’If no exterier then the shape is completly overlapping so can be removed
243 QuadList.Remove (.Part)
244 If i = 0 Then
245 CheckIndex -= 1
246 Else
247 MainIndex -= 1
248 ret = True
249 End If
250 Else
251 If .InternalVertices.Count > 0 Then
252 If .InternalVertices.Count = 4 Then
253 ’The shape is completly inside the other one
254 Else
255 ’Add connections between internals
256 Dim endPoints As ArrayList = getPointsIJoin2 (. ValidVertexPairs.ToArray , True)
257 Dim internalEndPoints As ArrayList
258259 If .InternalVertices.Count > 1 Then
260 If arrayHoldsPoint (. InternalVertices , otherShape.Part.P1) > -1 Then
261 If arrayHoldsPoint (. InternalVertices , otherShape.Part.P2) > -1 Then
262 validPairs.Add(New Point3D () {otherShape.Part.P1, otherShape.Part.P2})
263 ElseIf arrayHoldsPoint (. InternalVertices , otherShape.Part.P4) > -1 Then
264 validPairs.Add(New Point3D () {otherShape.Part.P1, otherShape.Part.P4})
265 End If
266 End If
267268 If arrayHoldsPoint (. InternalVertices , otherShape.Part.P2) > -1 Then
269 If arrayHoldsPoint (. InternalVertices , otherShape.Part.P3) > -1 Then
270 validPairs.Add(New Point3D () {otherShape.Part.P2, otherShape.Part.P3})
271 End If
272 End If
273274 If arrayHoldsPoint (. InternalVertices , otherShape.Part.P3) > -1 Then
275 If arrayHoldsPoint (. InternalVertices , otherShape.Part.P4) > -1 Then
276 validPairs.Add(New Point3D () {otherShape.Part.P3, otherShape.Part.P4})
277 End If
278 End If
79
279280 internalEndPoints = getPointsIJoin2(validPairs.ToArray , True)
281282 ’Merge these validPairs with the normal points
283 .ValidVertexPairs.AddRange(validPairs)
284 Else
285 internalEndPoints = New ArrayList
286 internalEndPoints.Add(. InternalVertices (0))
287 End If
288289 ’Now need to find the connections between the internal end points and the
290 ’end points of the new quad shape being constructed so far
291292 ConnectEndPointsToInternals (. ValidVertexPairs , internalEndPoints , otherShape , endPoints)
293294 If .IntersectionVertices.Count = 2 Then
295 ’Need to find if they are on the same line , or on different lines
296 Dim intVert0 As Point3D = DirectCast (. IntersectionVertices (0), Point3D)
297 Dim intVert1 As Point3D = DirectCast (. IntersectionVertices (1), Point3D)
298 Dim pointsOnSameLine As Boolean = _
299 (PointOnLine (.Part.P1 , .Part.P2, intVert0) AndAlso _
300 PointOnLine (.Part.P1, .Part.P2 , intVert1 )) Or _
301 (PointOnLine (.Part.P2 , .Part.P3, intVert0) AndAlso _
302 PointOnLine (.Part.P2, .Part.P3 , intVert1 )) Or _
303 (PointOnLine (.Part.P3 , .Part.P4, intVert0) AndAlso _
304 PointOnLine (.Part.P3, .Part.P4 , intVert1 )) Or _
305 (PointOnLine (.Part.P4 , .Part.P1, intVert0) AndAlso _
306 PointOnLine (.Part.P4, .Part.P1 , intVert1 ))
307308 If Not pointsOnSameLine Then
309 splitValidVertexPairs = getSplitPairs (. ValidVertexPairs , .IntersectionVertices , _
310 otherShape.Part)
311312 For Each ep As ArrayList In splitValidVertexPairs
313 Dim quads As ArrayList = MakeQuads(ep.ToArray , otherShape.Part , _
314 shapes (0). Part.Norm , False)
315316 Dim triangles As Point3D ()() = _
317 MakeTriangles(DirectCast(quads (0), Part_Quadrilateral ))
318319 Dim q2t1Area As Double = CalcTriangleArea(triangles (0))
320 Dim q2t2area As Double = CalcTriangleArea(triangles (1))
321 Dim thisQuad As Boolean = False
322 For Each iv As Point3D In .IntersectionVertices
323 If (Not DoesIntercept(triangles (0), q2t1Area , iv) = eInterceptType.None) Or _
324 (Not DoesIntercept(triangles (1), q2t2area , iv) = eInterceptType.None) Then
325 thisQuad = True
326 End If
327 Next
328 If thisQuad Then
329 ’Then this is the quad with the internal vertices in it.
330 ConnectEndPointsToInternals(ep, internalEndPoints , otherShape , _
331 .IntersectionVertices)
332 Exit For
333 End If
334 Next
335 Else
336 splitValidVertexPairs.Add(. ValidVertexPairs)
337 End If
338339 ElseIf .IntersectionVertices.Count = 1 Then
340 ’This will create two seperate shapes , with the intersection being where
341 ’they divide.
342 splitValidVertexPairs = getSplitPairs (. ValidVertexPairs , .IntersectionVertices , _
343 otherShape.Part)
344345 Else
346 splitValidVertexPairs.Add(. ValidVertexPairs)
347 End If
348 End If
349 Else
350 splitValidVertexPairs = New ArrayList
351 splitValidVertexPairs.Add(. ValidVertexPairs)
352 End If
353 ’Can now connect up as with 1.2.2a, which now comes through here too
354 ’Shapes with more than 4 vertices will be divided up correctly in the function called below
355 For Each pair As ArrayList In splitValidVertexPairs
356 If MakeAndAddQuadsToQuadList(pair.ToArray , otherShape.Part , .Part , QuadList , True) Then
357 If i = 0 Then
358 CheckIndex -= 1
359 Else
360 MainIndex -= 1
361 ret = True
362 End If
363 End If
364 Next
365 End If
366 End With
367 Next
368369 ’Returns true if the mainindex is removed , so know to exit while
370 Return ret
371 End Function
372373 Private Shared Sub ConnectEndPointsToInternals(ByRef ValidVertexPairs As ArrayList , _
80
374 ByVal internalEndPoints As ArrayList , ByVal OtherShape As Shape , ByVal EndPoints As ArrayList)
375376 For Each ip As Point3D In internalEndPoints
377 If ip.Equals(OtherShape.Part.P1) Then
378 For Each ep As Point3D In EndPoints
379 If PointOnLine(OtherShape.Part.P1, OtherShape.Part.P2, ep) Or _
380 PointOnLine(OtherShape.Part.P1, OtherShape.Part.P4 , ep) Then
381 ValidVertexPairs.Add(New Point3D () {ep, ip})
382 End If
383 Next
384 ElseIf ip.Equals(OtherShape.Part.P2) Then
385 For Each ep As Point3D In EndPoints
386 If PointOnLine(OtherShape.Part.P2, OtherShape.Part.P3, ep) Or _
387 PointOnLine(OtherShape.Part.P2, OtherShape.Part.P1 , ep) Then
388 ValidVertexPairs.Add(New Point3D () {ep, ip})
389 End If
390 Next
391 ElseIf ip.Equals(OtherShape.Part.P3) Then
392 For Each ep As Point3D In EndPoints
393 If PointOnLine(OtherShape.Part.P3, OtherShape.Part.P4, ep) Or _
394 PointOnLine(OtherShape.Part.P3, OtherShape.Part.P2 , ep) Then
395 ValidVertexPairs.Add(New Point3D () {ep, ip})
396 End If
397 Next
398 ElseIf ip.Equals(OtherShape.Part.P4) Then
399 For Each ep As Point3D In EndPoints
400 If PointOnLine(OtherShape.Part.P4, OtherShape.Part.P1, ep) Or _
401 PointOnLine(OtherShape.Part.P4, OtherShape.Part.P3 , ep) Then
402 ValidVertexPairs.Add(New Point3D () {ep, ip})
403 End If
404 Next
405 End If
406 Next
407 End Sub
408409 Private Shared Function getSplitPairs(ByVal ValidPairs As ArrayList , ByVal Intersections As ArrayList , _
410 ByVal CheckPart As Part_Quadrilateral) As ArrayList
411412 Dim ret As New ArrayList
413 Dim points () As Point3D
414 Dim testPoints As New ArrayList
415 Dim intPoint As Point3D = DirectCast(Intersections (0), Point3D)
416417 Dim index As Integer = 0
418 While index < ValidPairs.Count
419 points = DirectCast(ValidPairs(index), Point3D ())
420 ’Find where what the intersection point joins to. Then get the valid pairs from that point (having
421 ’removed the link to the intersection . This will return just that half , and then re -add the link
422 ’to the intersection to
423424 If points (0). Equals(intPoint) Then
425 ValidPairs.Remove(points)
426 testPoints.Add(points (1))
427 ElseIf points (1). Equals(intPoint) Then
428 ValidPairs.Remove(points)
429 testPoints.Add(points (0))
430 End If
431 ’Optimisation
432 If testPoints.Count = 2 Then Exit While
433 index += 1
434 End While
435436 Dim tmpValidPairs As New ArrayList
437 Dim vertices () As Point3D
438 For Each tp As Point3D In testPoints
439 tmpValidPairs.Clear ()
440 vertices = DirectCast(getPointsIJoin2(ValidPairs.ToArray , False , CheckPart , tp). ToArray(GetType(Point3D)), _
441 Point3D ())
442443 For i As Integer = vertices.Length - 2 To 0 Step -1
444 If Intersections.Count = 2 AndAlso vertices(i + 1). Equals(DirectCast(Intersections (1), Point3D )) Then
445 ’Make sure it jumps out at the other intersection point , so don ’t go all the way round
446 Exit For
447 End If
448 tmpValidPairs.Add(New Point3D () {vertices(i), vertices(i + 1)})
449 Next
450 tmpValidPairs.Add(New Point3D () {tp , intPoint })
451 ret.Add(tmpValidPairs.Clone)
452 Next
453 Return ret
454 End Function
455456 Private Shared Sub CalculateValidVertexPairs(ByVal MainShape As Shape , ByVal CheckShape As Shape)
457 ’All this cares about is valid vertex pairs which connect the external vertices of mainshape to any other
458 ’external , line interceptions , or intersections points which are outside of checkshape .
459460 ’It checks each of the part vertices for if it is external. If so will then check that it can connect
461 ’to the other correct vertices , or if there is a line intesection , or intersection in the way.
462463 Dim ret As New ArrayList
464 Dim allIntersections As New ArrayList
465466 ’Merge the intersectionVertices of each shape
467 MainShape.IntersectionVertices.AddRange(CheckShape.IntersectionVertices)
468 RemoveDuplicates(MainShape.IntersectionVertices)
81
469 CheckShape.IntersectionVertices = MainShape.IntersectionVertices
470471 Dim shapes () As Shape = New Shape() {MainShape , CheckShape}
472473 For i As Integer = 0 To 1
474 With shapes(i)
475 allIntersections.Clear ()
476 ret.Clear()
477 allIntersections.AddRange (. IntersectionVertices)
478 allIntersections.AddRange (. LineIntersections)
479480 ’P1 - which originally could connect to P2 and P4
481 If arrayHoldsPoint (. ExterierVertices , .Part.P1) > -1 Then
482 ’It is external now find if it can connect to P2
483 ’nearestPoint will return the end point if it can be seen or else the closest intersection
484 ret.Add(New Point3D () {.Part.P1 , nearestPoint (.Part.P1 , .Part.P2 , allIntersections )})
485 ret.Add(New Point3D () {.Part.P1 , nearestPoint (.Part.P1 , .Part.P4 , allIntersections )})
486 End If
487488 ’P2 - which originally could connect to P3 and P1
489 If arrayHoldsPoint (. ExterierVertices , .Part.P2) > -1 Then
490 ret.Add(New Point3D () {.Part.P2 , nearestPoint (.Part.P2 , .Part.P3 , allIntersections )})
491 ret.Add(New Point3D () {.Part.P2 , nearestPoint (.Part.P2 , .Part.P1 , allIntersections )})
492 End If
493494 ’P3 - which originally could connect to P4 and P2
495 If arrayHoldsPoint (. ExterierVertices , .Part.P3) > -1 Then
496 ret.Add(New Point3D () {.Part.P3 , nearestPoint (.Part.P3 , .Part.P4 , allIntersections )})
497 ret.Add(New Point3D () {.Part.P3 , nearestPoint (.Part.P3 , .Part.P2 , allIntersections )})
498 End If
499500 ’P4 - which originally could connect to P1 and P3
501 If arrayHoldsPoint (. ExterierVertices , .Part.P4) > -1 Then
502 ret.Add(New Point3D () {.Part.P4 , nearestPoint (.Part.P4 , .Part.P1 , allIntersections )})
503 ret.Add(New Point3D () {.Part.P4 , nearestPoint (.Part.P4 , .Part.P3 , allIntersections )})
504 End If
505506 ’Remove Duplicates
507508 Dim index1 As Integer = 0
509 Dim index2 As Integer = 0
510511 While index1 < ret.Count
512 index2 = index1 + 1
513 While index2 < ret.Count
514 If (DirectCast(ret(index1), Point3D ())(0). Equals(DirectCast(ret(index2), Point3D ())(0)) _
515 AndAlso DirectCast(ret(index1), Point3D ())(1). Equals(DirectCast(ret(index2), _
516 Point3D ())(1))) Or _
517 (DirectCast(ret(index1), Point3D ())(1). Equals(DirectCast(ret(index2), _
518 Point3D ())(0)) AndAlso DirectCast(ret(index1), Point3D ())(0). Equals( _
519 DirectCast(ret(index2), Point3D ())(1))) Then
520521 ’Pair at index2 is the same as the pair at index1 , so will remove the pair at index2
522 ret.RemoveAt(index2)
523 Exit While
524 End If
525 index2 += 1
526 End While
527 index1 += 1
528 End While
529530531 ’For to externals which link to each other this will cause to directional valid pairs i.e.
532 ’from A to B, and from B to A, but this isn ’t a problem
533 .ValidVertexPairs.Clear ()
534 .ValidVertexPairs.AddRange(ret)
535 End With
536 Next
537538 End Sub
539540 Private Shared Function nearestPoint(ByVal StartPoint As Point3D , ByVal EndPoint As Point3D , _
541 ByVal intersections As ArrayList) As Point3D
542543 ’Finds the direction from start point to end point , then checks against the intersection points in
544 ’the Shape to see if any others are the same direction , if so returns the closest , if none are then
545 ’returns the EndPoint
546547 Dim subVects As Point3D = SubVector(EndPoint , StartPoint)
548 Dim dir As Point3D = NormaliseVector(subVects)
549 Dim dist As Double = MagnitudeVector(subVects)
550 Dim closestDist As Double = dist
551 Dim closestPoint As Point3D = EndPoint
552 Dim tmpDist As Double
553 For Each p As Point3D In intersections
554 subVects = SubVector(p, StartPoint)
555 If NormaliseVector(subVects ). Equals(dir) Then
556 tmpDist = MagnitudeVector(subVects)
557 If tmpDist < closestDist Then
558 closestDist = tmpDist
559 closestPoint = p
560 End If
561 End If
562 Next
563
82
564 Return closestPoint
565 End Function
566567 Private Shared Function arrayHoldsPoint(ByVal array As ArrayList , ByVal point As Point3D) As Integer
568 Dim index As Integer
569 For Each p As Point3D In array
570 If p.Equals(point) Then Return index
571 index += 1
572 Next
573 Return -1
574 End Function
575576 Private Shared Function MakeAndAddQuadsToQuadList(ByVal ValidPairs () As Object , _
577 ByVal OtherPart As Part_Quadrilateral , ByVal OwnPart As Part_Quadrilateral , _
578 ByRef quadList As ArrayList , ByVal ConainsInternalVertices As Boolean) As Boolean
579580 Dim newQuads As ArrayList = MakeQuads(ValidPairs , OtherPart , OwnPart.Norm , ConainsInternalVertices)
581 If newQuads IsNot Nothing AndAlso newQuads.Count > 0 Then
582 ’If the new part is the same as the old part , then don ’t add it to the list ,
583 ’and continue to next part
584 If newQuads.Count = 1 AndAlso CheckParts(DirectCast(newQuads (0), Part_Quadrilateral), OwnPart) Then
585 Return False
586 Else
587 ’1.2.6 - Add new quads to the list
588 For Each q As Part_Quadrilateral In newQuads
589 q.RealColour = OwnPart.RealColour
590 q.colour = OwnPart.colour
591 q.Norm = OwnPart.Norm
592593 quadList.Add(q)
594 Next
595596 ’remove the old one
597 quadList.Remove(OwnPart)
598 Return True
599 End If
600 End If
601602 End Function
603604 Private Shared Function CheckParts(ByVal Part1 As Part_Quadrilateral , ByVal Part2 As Part_Quadrilateral) As Boolean
605 Dim part1Vs As New ArrayList
606 Dim part2Vs As New ArrayList
607608 With Part1
609 part1Vs.AddRange(New Point3D () {.P1 , .P2, .P3 , .P4})
610 End With
611612 With Part2
613 part2Vs.AddRange(New Point3D () {.P1 , .P2, .P3 , .P4})
614 End With
615616 Dim count As Integer = 0
617 For Each p1 As Point3D In part1Vs
618 If arrayHoldsPoint(part2Vs , p1) > -1 Then count += 1
619 Next
620 ’If count = 4 then all the new vertices were in the old shape , and they are the same
621 Return count = 4
622 End Function
623624 Private Shared Function MakeQuads(ByVal ValidPairs () As Object , ByVal CheckPart As Part_Quadrilateral , _
625 ByVal Norm As Point3D , ByVal ConainsInternalVertices As Boolean) As ArrayList
626627 ’1.2.2a - No internal points , so keep valid vertex pairs (found when finding line intersections )
628 ’and join to these to the remaining vertices if it doesn ’t mean passing through CheckPart
629630 Dim ret As New ArrayList
631632 Dim newQuadObj As Part_Quadrilateral
633 Dim newQuad As ArrayList = getPointsIJoin2(ValidPairs , False , CheckPart)
634 If newQuad Is Nothing Then Return Nothing
635 ’Its a valid quad
636 ’Now make correct orientation and then create Part_Quadrilateral object
637 newQuadObj = MakeQuad(newQuad , Norm , ConainsInternalVertices)
638 If Not newQuadObj Is Nothing Then
639 ret.Add(newQuadObj)
640 End If
641642 ’Removes used points , so next time round the next correct quad can be made
643 Dim points () As Point3D
644 Dim pairsIndex As Integer
645 For Each p As Point3D In newQuad
646 pairsIndex = 0
647 While pairsIndex < ValidPairs.Length
648 points = DirectCast(ValidPairs(pairsIndex), Point3D ())
649 If points (0). Equals(p) OrElse points (1). Equals(p) Then
650 ValidPairs = removeObject(ValidPairs , pairsIndex)
651 If ValidPairs.Length = 0 Then Exit For
652 Else
653 pairsIndex += 1
654 End If
655 End While
656 Next
657 If ValidPairs.Length > 0 Then
658 Dim tmpQuads As ArrayList = MakeQuads(ValidPairs , CheckPart , Norm , ConainsInternalVertices)
83
659 If Not tmpQuads Is Nothing Then
660 ret.AddRange(tmpQuads)
661 End If
662 End If
663664 Return ret
665666 End Function
667668 Private Shared Function MakeQuad(ByVal vertices As ArrayList , ByVal Norm As Point3D , _
669 ByVal ContainsInternalVertices As Boolean) As Part_Quadrilateral
670671 ’1.2.5 - Create Part_Quadrilateral Object from sorted vertices
672673 ’Need to check that do have 4 points , and if no duplicate the final point until I do have 4 points!
674 If vertices.Count = 3 Then
675 vertices.Add(vertices.Item(vertices.Count - 1))
676 ElseIf vertices.Count < 3 Then
677 Return Nothing
678 ElseIf vertices.Count > 4 Then
679 If ContainsInternalVertices = False Then
680 ’Have too many vertices , need to either detect a vertex in the middle of a straight line , or
681 ’cut the shape into multiple quads
682683 ’Test if a vertex on a straight line between two others
684685 ’1.2.4a - Check if vertex is on the same line as that between two other vertices , if so remove
686 ’the middle one. Only if there are no internal vertices to the shape
687 Dim index1 , index2 , index3 As Integer
688 Dim counter2 , counter3 As Integer
689690 Dim initialPoint As Point3D
691 Dim currentTestPoint , testPoint , testDir As Point3D
692 Dim testDist As Double
693 Dim removed As Boolean = False
694695 Dim SubVects As Point3D
696697 While vertices.Count > 4
698 Dim initialVerticesCount As Integer = vertices.Count
699 index1 = 0
700 While index1 < (vertices.Count - 1)
701 initialPoint = DirectCast(vertices(index1), Point3D)
702 index2 = index1
703 counter2 = 0
704705 While counter2 < vertices.Count - 1
706 index2 = (index2 + 1) Mod vertices.Count
707 testPoint = DirectCast(vertices(index2), Point3D)
708 SubVects = SubVector(testPoint , initialPoint)
709 testDir = NormaliseVector(SubVects)
710 testDist = MagnitudeVector(SubVects)
711 counter3 = 0
712713 index3 = index2
714 While counter3 < (vertices.Count - 2)
715 index3 = (index3 + 1) Mod vertices.Count
716 currentTestPoint = DirectCast(vertices(index3), Point3D)
717 SubVects = SubVector(currentTestPoint , initialPoint)
718 If NormaliseVector(SubVects ). Equals(testDir) Then
719 If MagnitudeVector(SubVects) < testDist Then
720 vertices.Remove(currentTestPoint)
721 removed = True
722 Exit While
723 End If
724 End If
725726 counter3 += 1
727 End While
728 If removed Then Exit While
729 counter2 += 1
730 End While
731 If removed Then Exit While
732 index1 += 1
733 End While
734735 removed = False
736 If vertices.Count = initialVerticesCount Then
737 MsgBox("PANIC")
738 Exit While
739 End If
740 End While
741 Else
742 ’Need to sort out shapes with internals which correctly gives shapes with more than four vertices
743 Dim i As Integer = 0
744 End If
745 End If
746747 If vertices.Count > 4 Then Return Nothing
748749 ’1.2.3 - Sort vertices into counter clockwise direction
750751 ’If the signed area is positive then the vertices are counter clockwise
752 ’ Equation for signed area - 2 A(P) = (N . (sum_{i=0}^{n -1} (v_i x v_{i+1})))
753
84
754 Dim verts() As Point3D = DirectCast(vertices.ToArray(GetType(Point3D)), Point3D ())
755756 Dim SumOfCrossProducts As Point3D = AddVector(CrossProductVector(verts (0), verts (1)), _
757 AddVector(CrossProductVector(verts(1), verts (2)), _
758 CrossProductVector(verts (2), verts (3))))
759760 Dim ret As New Part_Quadrilateral
761 If DotProductVector(Norm , SumOfCrossProducts) > 0 Then
762 ret.P1 = verts (0)
763 ret.P2 = verts (1)
764 ret.P3 = verts (2)
765 ret.P4 = verts (3)
766 Else
767 ret.P1 = verts (0)
768 ret.P2 = verts (3)
769 ret.P3 = verts (2)
770 ret.P4 = verts (1)
771 End If
772773 Return ret
774 End Function
775776 Private Shared Function getPointsIJoin2(ByVal ValidPairs () As Object , ByVal ReturnEndPoints As Boolean , _
777 Optional ByVal CheckPart As Part_Quadrilateral = Nothing , _
778 Optional ByVal TestPoint As Point3D = Nothing) As ArrayList
779780 ’Find an end , or find that valid pairs define a complete loop , and then start adding points
781 ’either from the end , or from the start point
782783 ’Have to specify checkpart if want all the points , not just the end points
784 If ReturnEndPoints = False And CheckPart Is Nothing Then Return Nothing
785786 If TestPoint Is Nothing Then TestPoint = DirectCast(ValidPairs (0), Point3D ())(0)
787 Dim tester As Point3D = TestPoint
788 Dim previous As Point3D = Nothing
789 Dim foundPoint As Boolean = False
790 Dim startPoint As Point3D = Nothing
791 Dim realStartPoint As Point3D
792793 Do
794 If startPoint Is Nothing OrElse Not tester.Equals(startPoint) Then
795 If startPoint Is Nothing Then startPoint = TestPoint
796 For Each points () As Point3D In ValidPairs
797 If points (0). Equals(tester) And (previous Is Nothing OrElse Not points (1). Equals(previous )) Then
798 previous = tester
799 tester = points (1)
800 foundPoint = True
801 Exit For
802 ElseIf points (1). Equals(tester) And (previous Is Nothing OrElse Not points (0). Equals(previous )) Then
803 previous = tester
804 tester = points (0)
805 foundPoint = True
806 Exit For
807 End If
808 Next
809 End If
810 If Not foundPoint Then
811 ’We didn ’t find a point , so is either the end or we have circled back to the startpoint
812 realStartPoint = tester
813 Exit Do
814 Else
815 foundPoint = False
816 End If
817 Loop
818819 Dim ret As New ArrayList
820 ret.Add(realStartPoint)
821 startPoint = Nothing
822 foundPoint = False
823 tester = realStartPoint
824 previous = Nothing
825826 Do
827 ’If startPoint Is Nothing Then
828 If startPoint Is Nothing Then startPoint = realStartPoint
829 For Each points () As Point3D In ValidPairs
830 If points (0). Equals(tester) And (previous Is Nothing OrElse Not points (1). Equals(previous )) Then
831 If points (1). Equals(startPoint) Then Exit For
832 previous = tester
833 tester = points (1)
834 foundPoint = True
835 ret.Add(tester)
836 Exit For
837 ElseIf points (1). Equals(tester) And (previous Is Nothing OrElse Not points (0). Equals(previous )) Then
838 If points (0). Equals(startPoint) Then Exit For
839 previous = tester
840 tester = points (0)
841 foundPoint = True
842 ret.Add(tester)
843 Exit For
844 End If
845 Next
846 ’End If
847 If (Not foundPoint) Or (ret.Count > 100) Then ’ValidPairs .Length) Then
848 ’We didn ’t find a point , so is either the end or we have circled back to the startpoint
85
849 ’and now have our list of vertices
850851 ’If ret.Count > ValidPairs .Length then there has been a problem. Most likely the
852 ’valid pairs array has not been created properly , and has points linking back to the
853 ’wrong points. This will happen if the intersection between two quads isn ’t properly
854 ’recognised . If there is an error this loop will continue indefinitly just jumping between the
855 ’same points. This will jump out if it has gone wrong. It won ’t create a proper shape though.
856 ’Changed to a catch of 100, as there won ’t be shapes created with 100 vertices
857 ’this needed changing as there will be more vertices than valid pairs when
858 ’running test 1.2.2d, and having shapes created with more than for vertices due
859 ’to internal vertices.
860 Exit Do
861 Else
862 foundPoint = False
863 End If
864 Loop
865866867 ’The first point will be one of the open ends , and the last point the other open end. Then just
868 ’need to check these can join!
869 Dim pStart As Point3D = DirectCast(ret(0), Point3D)
870 Dim pEnd As Point3D = DirectCast(ret(ret.Count - 1), Point3D)
871872 If ReturnEndPoints Then
873 ret.Clear ()
874 ret.Add(pStart)
875 ret.Add(pEnd)
876 Return ret
877 End If
878879 If CanSee(pStart , pEnd , CheckPart) AndAlso (Not LineInside(pStart , pEnd , CheckPart )) Then
880 Return ret
881 Else
882 MsgBox("Error joining first and last vertices")
883 Return Nothing
884 End If
885886 End Function
887888 Private Shared Function CanSee(ByVal v1 As Point3D , ByVal v2 As Point3D , ByVal checkpart As Part_Quadrilateral) _
889 As Boolean
890891 ’Detects if vertex 1 (v1) can join with vertex 2 (v2) without intercepting with the checkpart
892893 Dim Index1 , Index2 As Integer
894 Dim checkVertices () As Point3D
895896 With checkpart
897 checkVertices = New Point3D () {.P1, .P2, .P3, .P4}
898 End With
899 For i As Integer = 0 To 3
900 Index1 = i
901 If i < 3 Then
902 Index2 = i + 1
903 Else
904 Index2 = 0
905 End If
906907 If GetLineInterceptionPoint(v1 , v2 , checkVertices(Index1), checkVertices(Index2 )) IsNot Nothing Then
908 Return False
909 End If
910 Next
911912 Return True
913 End Function
914915 Private Shared Sub DisplayIntersections(ByVal shp1 As Shape , ByVal shp2 As Shape , ByRef LM As LegoModel)
916 ’Adds crosses to the final legoModel (LM), these are then displayed on the final model.
917 ’Yellow cross for vertices intersecting
918 ’Green cross for internal vertices
919 ’Cyan cross for line intersections
920921 Dim shapes () As Shape = New Shape() {shp1 , shp2}
922 For Each s As Shape In shapes
923 For Each p As Point3D In s.IntersectionVertices
924 Dim lp As New Part_Line
925 lp.RealColour = New Single () {1, 1, 0, 1}
926 lp.P1 = New Point3D(p.x + 1, p.y + 1, p.z)
927 lp.P2 = New Point3D(p.x - 1, p.y - 1, p.z)
928 Dim lp2 As New Part_Line
929 lp2.RealColour = New Single () {1, 1, 0, 1}
930 lp2.P1 = New Point3D(p.x + 1, p.y - 1, p.z)
931 lp2.P2 = New Point3D(p.x - 1, p.y + 1, p.z)
932 LM.addPart(lp)
933 LM.addPart(lp2)
934 Next
935936 For Each p As Point3D In s.InternalVertices
937 Dim lp As New Part_Line
938 lp.RealColour = New Single () {0, 1, 0, 1}
939 lp.P1 = New Point3D(p.x, p.y + 1, p.z + 1)
940 lp.P2 = New Point3D(p.x, p.y - 1, p.z - 1)
941 Dim lp2 As New Part_Line
942 lp2.RealColour = New Single () {0, 1, 0, 1}
943 lp2.P1 = New Point3D(p.x, p.y - 1, p.z + 1)
86
944 lp2.P2 = New Point3D(p.x, p.y + 1, p.z - 1)
945 LM.addPart(lp)
946 LM.addPart(lp2)
947 Next
948949 For Each p As Point3D In s.LineIntersections
950 Dim lp As New Part_Line
951 lp.RealColour = New Single () {0, 1, 1, 1}
952 lp.P1 = New Point3D(p.x - 1, p.y, p.z + 1)
953 lp.P2 = New Point3D(p.x + 1, p.y, p.z - 1)
954 Dim lp2 As New Part_Line
955 lp2.RealColour = New Single () {0, 1, 1, 1}
956 lp2.P1 = New Point3D(p.x + 1, p.y, p.z + 1)
957 lp2.P2 = New Point3D(p.x - 1, p.y, p.z - 1)
958 LM.addPart(lp)
959 LM.addPart(lp2)
960 Next
961 Next
962 End Sub
963964 Private Shared Function GetQuads(ByVal Parts As ArrayList) As ArrayList
965 Dim ret As New ArrayList
966 For Each p As LegoPart In Parts
967 Select Case p.type
968 Case PartType.PartFile
969 With DirectCast(p, Part_External)
970 If Not .Stud Then ’remove studs
971 ret.AddRange(GetQuads (.part.Parts ))
972 End If
973 End With
974 Case PartType.Quadrilateral
975 ret.Add(p)
976 End Select
977 Next
978 Return ret
979 End Function
980981 Private Shared Sub Check1_1_1(ByRef Quads () As Part_Quadrilateral)
982 ’If all 4 vertices are the same then remove (this is an optimisation , as this case would be found in 1.1.2
983 ’however that is more complex , and so reducing the number of quadrilaterals to be checked here speeds that
984 ’process up. This is also quite a common case if bricks are placed next to each other , or end to end.
985986 Dim mainIndex As Integer
987 Dim checkIndex As Integer
988 Dim mainPart As Part_Quadrilateral
989 Dim checkPart As Part_Quadrilateral
990991 While mainIndex < Quads.Length
992 ’If new parts are added to quads this will still work , whereas the inbuilt for each will fail
993 ’if the list changes
994995 mainPart = Quads(mainIndex)
996 ’Only check the items following the main part in the list (as the previous ones have already
997 ’been check against this main part)
998999 For checkIndex = mainIndex + 1 To Quads.Length - 1
1000 checkPart = Quads(checkIndex)
1001 If CheckParts(mainPart , checkPart) Then ’1.1.1 - If all 4 vertices are the same remove both quads
1002 ’Done in this order , because checkpart is is always below mainpart , and therefore will not
1003 ’affect the mainindex
1004 Quads = removeQuad(Quads , checkIndex)
1005 Quads = removeQuad(Quads , mainIndex)
1006 mainIndex -= 1 ’Done , else will skip the next item (which is now in the mainIndex location)
1007 Exit For
1008 End If
1009 Next
1010 mainIndex += 1
1011 End While
1012 End Sub
10131014 Private Shared Function CheckIfPointIsVertex(ByVal Point As Point3D , ByVal Quad As Part_Quadrilateral) As Boolean
1015 ’Checks if Point is one of the vertices on the quad
1016 With Point
1017 Return .Equals(Quad.P1) Or .Equals(Quad.P2) Or .Equals(Quad.P3) Or .Equals(Quad.P4)
1018 End With
1019 End Function
10201021 Private Shared Function IntersectVertexCheck1_1_2(ByVal Quad1 As Part_Quadrilateral , _
1022 ByVal Quad2 As Part_Quadrilateral , ByVal allowVertices As Boolean) As Shape
10231024 ’Both 1.1.2 and 1.1.3 are carried out here
1025 ’1.1.2 - Find if mainpart vertices are inside or outside the check quad
1026 ’1.1.3 - Find interception of boundary lines
10271028 ’1.1.2 - Starts here
10291030 ’Split into two triangles .
1031 ’Given corners of triangle are A, B, C and given point is D, then D is inside iff
1032 ’area(ABC) = area(ABD) + area(ACD) + area(BCD ). Point intersects if one of the areas is 0
10331034 ’Area of triangle = 0.5|(B-A)x(C-A)| (cross product)
10351036 ’Triangle 1 = p1 ,p2 ,p3 Triangle 2 = p1 ,p3 ,p4
10371038 ’e.g. q1t1 = quad 1, triangle 1
87
1039 Dim Triangles ()() As Point3D = MakeTriangles(Quad2)
10401041 ’Calculate triangle Areas to test against
1042 Dim q2t1Area As Double = CalcTriangleArea(Triangles (0))
1043 Dim q2t2area As Double = CalcTriangleArea(Triangles (1))
10441045 Dim q1vertices () As Point3D = {Quad1.P1, Quad1.P2 , Quad1.P3 , Quad1.P4}
1046 Dim q2vertices () As Point3D = {Quad2.P1, Quad2.P2 , Quad2.P3 , Quad2.P4}
10471048 ’Check for quad1 intersecting
1049 Dim quad1Shape As New Shape
1050 quad1Shape.Part = Quad1
10511052 For Each q1vertex As Point3D In q1vertices
1053 ’check if vertex is the same as one in the other shape
1054 If CheckIfPointIsVertex(q1vertex , Quad2) Then
1055 quad1Shape.IntersectionVertices.Add(q1vertex)
1056 Else
1057 Select Case (DoesIntercept(Triangles (0), q2t1Area , q1vertex ))
1058 Case eInterceptType.Intercept
1059 quad1Shape.IntersectionVertices.Add(q1vertex)
1060 Case eInterceptType.Internal
1061 quad1Shape.InternalVertices.Add(q1vertex)
1062 Case eInterceptType.None
1063 Select Case (DoesIntercept(Triangles (1), q2t2area , q1vertex ))
1064 Case eInterceptType.Intercept
1065 quad1Shape.IntersectionVertices.Add(q1vertex)
1066 Case eInterceptType.Internal
1067 quad1Shape.InternalVertices.Add(q1vertex)
1068 Case eInterceptType.None
1069 quad1Shape.ExterierVertices.Add(q1vertex)
1070 End Select
1071 End Select
1072 End If
1073 Next
10741075 ’1.1.3 - Find interception of the boundary lines
10761077 ’Checks edge against every other edge on the other quad
1078 Dim thisLineIntersections As New ArrayList
1079 Dim quad1index , quad1index2 , quad2index , quad2index2 As Integer
1080 Dim v1, v2 As Point3D
1081 For quad1index = 0 To 3
1082 ’When index is 3 then we create the line from 3 back to 0
1083 quad1index2 = (( quad1index Mod 3) + 1) - CInt(Int(quad1index / 3))
1084 v1 = q1vertices(quad1index)
1085 v2 = q1vertices(quad1index2)
1086 thisLineIntersections.Clear()
10871088 For quad2index = 0 To 3
1089 ’When index is 3 then we create the line from 3 back to 0
1090 quad2index2 = (( quad2index Mod 3) + 1) - CInt(Int(quad2index / 3))
1091 Dim interception As Point3D = _
1092 GetLineInterceptionPoint(v1, v2, q2vertices(quad2index), q2vertices(quad2index2 ))
1093 If interception IsNot Nothing Then
1094 quad1Shape.LineIntersections.Add(interception)
1095 thisLineIntersections.Add(interception)
1096 End If
1097 Next
1098 Next
10991100 ’These methods will find overlapping interceptions , therefore the interception list is cleared of duplications
1101 ’Dim index , index2 As Integer
11021103 RemoveDuplicates(quad1Shape.IntersectionVertices)
1104 RemoveDuplicates(quad1Shape.LineIntersections)
11051106 Return quad1Shape
1107 End Function
11081109 Private Shared Function LineInside(ByVal p1 As Point3D , ByVal p2 As Point3D , ByVal testQuad As Part_Quadrilateral) _
1110 As Boolean
11111112 ’To detect if the line is inside , points p1 , and p2 will be brought together very slightly
1113 ’(i.e. shorten the line ). Then will test if either of these points are inside the test quad
1114 ’then the function will return false , else will return true
11151116 ’Finds vector direction from p1 to p2 (i.e. the normal vector in this direction
1117 Dim triangles ()() As Point3D = MakeTriangles(testQuad)
1118 ’Make it a half unit to shorten the line from each end
1119 Dim dir As Point3D = ScaleVector(NormaliseVector(SubVector(p2 , p1)), 0.5)
1120 Dim testPoints () As Point3D = New Point3D () {AddVector(p1, dir), SubVector(p2, dir)}
1121 Dim ret As Boolean = True
1122 Dim tmpRet As Boolean = False
11231124 For Each p As Point3D In testPoints
1125 For Each t() As Point3D In triangles
1126 ’If one or the other triangle has the point then tmpRet will be true
1127 tmpRet = tmpRet Or (DoesIntercept(t, CalcTriangleArea(t), p) = eInterceptType.Internal)
1128 Next
1129 ’If all the points are internal then ret will be true , else will be false
1130 ret = ret And tmpRet
1131 tmpRet = False
1132 Next
1133 Return ret
88
1134 End Function
11351136 Private Shared Function GetLineInterceptionPoint(ByVal L1P1 As Point3D , ByVal L1P2 As Point3D , _
1137 ByVal L2P1 As Point3D , ByVal L2P2 As Point3D) As Point3D
11381139 ’Find if lines are skew , if so they don ’t intercept
1140 ’(x1 - x3 ).[( x2 - x1)x(x4 - x3)] != 0
1141 ’a = (x1 - x3), b=(x2 - x1), c=(x4 - x3)
11421143 Dim a, b, c As Point3D
11441145 a = SubVector(L1P1 , L2P1)
1146 b = SubVector(L1P2 , L1P1)
1147 c = SubVector(L2P2 , L2P1)
1148 If DotProductVector(a, CrossProductVector(b, c)) = 0 Then
1149 ’don ’t skew , so can now check for interception
11501151 ’x = x1 + b ( [(a x c) . (b x c)] / |b x c| ^ 2 )
1152 ’x is point of interception , updated a = x3 - x1 now
1153 ’d = a x c, e = b x c, f = |b x c|^2
11541155 a = SubVector(L2P1 , L1P1)
1156 Dim d, e, x As Point3D
1157 Dim f As Double
1158 d = CrossProductVector(a, c)
1159 e = CrossProductVector(b, c)
1160 f = CrossProductMagnitude(b, c) ^ 2
11611162 Dim tmp As Single = CSng(DotProductVector(d, e) / f)
1163 x = AddVector(L1P1 , ScaleVector(b, tmp))
1164 If Single.IsNaN(x.x) Then
1165 ’No interception
1166 Else
1167 ’This gives interceptions of infinite length lines , so need to check that the actual
1168 ’interception is within the length of the line
1169 If IsCorrectInterception(L1P1 , L1P2 , L2P1 , L2P2 , x) Then
1170 Return x
1171 End If
1172 End If
1173 End If
11741175 Return Nothing
1176 End Function
11771178 Private Shared Function IsCorrectInterception(ByVal L1P1 As Point3D , ByVal L1P2 As Point3D , _
1179 ByVal L2P1 As Point3D , ByVal L2P2 As Point3D , ByVal Interception As Point3D) As Boolean
11801181 ’Sort the min and max of each dimension
1182 Dim lines ()() As Point3D = New Point3D ()() {New Point3D () {L1P1 , L1P2}, New Point3D () {L2P1 , L2P2}}
1183 Dim ret As Boolean
11841185 ’See if want to allow vertices to be collided with or not ,
1186 ’if not then don ’t include the vertices of quad 1 and quad 2
11871188 ’AllowVertices means ’It is allowed to collide with vertices , and still be a correct Interception
1189 If (Interception.IntEquals(L1P1) Or Interception.IntEquals(L1P2) Or Interception.IntEquals(L2P1) Or _
1190 Interception.IntEquals(L2P2)) Then
1191 Return False
1192 End If
11931194 For Each Line As Point3D () In lines
11951196 Dim xMin , xMax , yMin , yMax , zMin , zMax As Single
1197 If Line (0).x > Line (1).x Then
1198 xMin = Line (1).x : xMax = Line (0).x
1199 Else
1200 xMin = Line (0).x : xMax = Line (1).x
1201 End If
12021203 If Line (0).y > Line (1).y Then
1204 yMin = Line (1).y : yMax = Line (0).y
1205 Else
1206 yMin = Line (0).y : yMax = Line (1).y
1207 End If
12081209 If Line (0).z > Line (1).z Then
1210 zMin = Line (1).z : zMax = Line (0).z
1211 Else
1212 zMin = Line (0).z : zMax = Line (1).z
1213 End If
12141215 With Interception
1216 ret = (.x >= xMin And .x <= xMax And .y >= yMin And .y <= yMax And .z >= zMin And .z <= zMax)
1217 End With
12181219 ’It it fails with the first line , don ’t need to check with the second , and will return false
1220 ’if second line fails
1221 If ret = False Then Return False
1222 Next
12231224 ’Nether line fails so interception ok
1225 Return True
1226 End Function
12271228 Private Shared Function DoesIntercept(ByVal Triangle () As Point3D , ByVal TriangleArea As Double , _
89
1229 ByVal point As Point3D) As eInterceptType
12301231 Dim checkArea1 As Double = CalcTriangleArea(New Point3D () {Triangle (0), Triangle (1), point})
1232 Dim checkArea2 As Double = CalcTriangleArea(New Point3D () {Triangle (0), Triangle (2), point})
1233 Dim checkArea3 As Double = CalcTriangleArea(New Point3D () {Triangle (1), Triangle (2), point})
12341235 ’checkArea2 is the triangle formed along the edge in the middle of the
1236 ’quad , see makeTriangles . Therefore if this area is 0 it is actually an internal
1237 ’point , not an interception . Vertices have been removed before the function so
1238 ’won ’t be wrongly specified
1239124012411242 If Math.Round(TriangleArea) = Math.Round(checkArea1 + checkArea2 + checkArea3) Then
1243 ’Only if the quad norms are the same is the vertex counted as internal , as it will need to cut the shape
1244 ’its overlapping with , if the normals aren ’t the same then this won ’t need to be done , and therefore is
1245 ’counted as an interception
1246 ’### Update , quads in different direction don ’t count as any kind of interception , and will not reach
1247 ’this function
12481249 ’If CInt( checkArea2 ) = 0 then this is internal , as this tests along the
1250 ’internal diagonal through the quad formed by spliting it into two triangles
1251 If CInt(checkArea1) = 0 Or CInt(checkArea3) = 0 Then
1252 Return eInterceptType.Intercept
1253 Else
1254 Return eInterceptType.Internal
1255 End If
1256 Else
1257 Return eInterceptType.None
1258 End If
1259 End Function
12601261 Private Shared Function CalcTriangleArea(ByVal Points () As Point3D) As Double
1262 Dim ret As Double = Math.Abs(CrossProductMagnitude(SubVector(Points (1), Points (0)), _
1263 SubVector(Points (2), Points (0)))) / 2
1264 If Double.IsNaN(ret) Then ret = 0
1265 Return ret
1266 End Function
12671268 Private Shared Function removeObject(ByVal array () As Object , ByVal Index As Integer) As Object ()
1269 Dim ret(array.Length - 2) As Object
1270 Dim writeIndex As Integer = 0
1271 For i As Integer = 0 To array.Length - 1
1272 If i <> Index Then
1273 ret(writeIndex) = array(i)
1274 writeIndex += 1
1275 End If
1276 Next
1277 Return ret
1278 End Function
12791280 Private Shared Function removeQuad(ByVal Quads As Part_Quadrilateral (), ByVal Index As Integer) _
1281 As Part_Quadrilateral ()
1282 Dim ret(Quads.Length - 2) As Part_Quadrilateral
1283 Dim writeIndex As Integer = 0
1284 For i As Integer = 0 To Quads.Length - 1
1285 If i <> Index Then
1286 ret(writeIndex) = Quads(i)
1287 writeIndex += 1
1288 End If
1289 Next
1290 Return ret
1291 End Function
12921293 Private Shared Function PointOnLine(ByVal L1P1 As Point3D , ByVal L1P2 As Point3D , ByVal TestPoint As Point3D) _
1294 As Boolean
1295 ’If direction between L1P1 and L1P2 is the same as that betwenn L1P1 and
1296 ’testPoint , and that testPoint is closer to L1P1 than L1P2 is then its on the line
1297 If L1P1.Equals(TestPoint) Or L1P2.Equals(TestPoint) Then
1298 ’If point is one of the vertices then its on the line
1299 Return True
1300 End If
1301 If NormaliseVector(SubVector(L1P2 , L1P1 )). Equals(NormaliseVector(SubVector(TestPoint , L1P1 ))) Then
1302 Return MagnitudeVector(SubVector(TestPoint , L1P1)) <= MagnitudeVector(SubVector(L1P2 , L1P1))
1303 End If
1304 Return False
1305 End Function
1306 End Class
90
D.10 Transformer.vb
Applies the transformations to all the parts within the internal representation afterloading, and calculates the normals, and correct colours of all the parts.
1 Imports LEGO_Smoothing.ModelDefs
2 Imports LEGO_Smoothing.Common
34 Public Class Transformer
56 Private Shared TransformMatrixStack As New Stack
7 Private Const DefaultCurCol As Integer = 7
89 Public Shared Sub TransformModel(ByVal LM As LegoModel)
10 InternalTransformModel(LM, DefaultCurCol , False)
11 End Sub
1213 Private Shared Sub InternalTransformModel(ByVal LM As LegoModel , ByVal curCol As Integer , ByVal Invert As Boolean)
14 ’This sub takes all the loaded parts , and applies the transformations specified when loading , and also sets the
15 ’colours so it doesn ’t have to keep doing the lookup on each frame , and sorts inversions of the
16 ’individual items. Its done here as the models are static , so don ’t need to change once loaded , and saves doing
17 ’it on the fly for each frame , which causes very low FPS rate.
1819 If LM IsNot Nothing AndAlso LM.Parts IsNot Nothing Then
20 For Each p As LegoPart In LM.Parts
21 Select Case p.type
22 Case PartType.PartFile
23 TransformExternal(DirectCast(p, Part_External), curCol)
24 Case PartType.Line
25 TransformLine(DirectCast(p, Part_Line), curCol)
26 Case PartType.Triangle
27 TransformTriangle(DirectCast(p, Part_Triangle), curCol , Invert)
28 Case PartType.Quadrilateral
29 TransformQuad(DirectCast(p, Part_Quadrilateral), curCol , Invert)
30 Case PartType.SpecialLine
31 TransformSpecialLine(DirectCast(p, Part_SpecialLine), curCol)
32 End Select
33 Next
34 End If
35 End Sub
3637 Private Shared Sub TransformExternal(ByVal p As Part_External , ByVal curCol As Integer)
38 Dim usedCol As Integer
3940 TransformMatrixStack.Push(p.transformMatrix)
4142 Select Case p.colour
43 Case 16 ’Current Colour
44 usedCol = curCol
45 Case 24 ’Reverse Of Current Colour
46 usedCol = GetReversedCol(curCol)
47 Case Else ’All other cases the colour has been given so use it
48 usedCol = p.colour
49 End Select
5051 InternalTransformModel(p.part , usedCol , p.inverted)
52 TransformMatrixStack.Pop()
53 End Sub
5455 Private Shared Sub TransformLine(ByVal p As Part_Line , ByVal curCol As Integer)
56 p.P1 = TransformPoint(p.P1)
57 p.P2 = TransformPoint(p.P2)
5859 p.RealColour = GetColorArray(p.colour , curCol)
60 End Sub
6162 Private Shared Sub TransformTriangle(ByVal p As Part_Triangle , ByVal curCol As Integer , ByVal Invert As Boolean)
63 ’p.Norm = TransformPoint (p.Norm)
6465 If Invert Then
66 Dim tmpPoint As Point3D = TransformPoint(p.P3)
67 p.P2 = TransformPoint(p.P2)
68 p.P3 = TransformPoint(p.P1)
69 p.P1 = tmpPoint.Copy
70 Else
71 p.P1 = TransformPoint(p.P1)
72 p.P2 = TransformPoint(p.P2)
73 p.P3 = TransformPoint(p.P3)
74 End If
7576 p.Norm = Common.CalcNormal(New Point3D () {p.P1, p.P2, p.P3})
7778 p.RealColour = GetColorArray(p.colour , curCol)
79 End Sub
8081 Private Shared Sub TransformQuad(ByVal p As Part_Quadrilateral , ByVal curCol As Integer , ByVal Invert As Boolean)
82 ’p.Norm = TransformPoint (p.Norm)
8384 If Invert Then
85 Dim tmpPoint As Point3D = TransformPoint(p.P3)
86 p.P2 = TransformPoint(p.P2)
87 p.P3 = TransformPoint(p.P1)
88 p.P4 = TransformPoint(p.P4)
91
89 p.P1 = tmpPoint.Copy
90 Else
91 p.P1 = TransformPoint(p.P1)
92 p.P2 = TransformPoint(p.P2)
93 p.P3 = TransformPoint(p.P3)
94 p.P4 = TransformPoint(p.P4)
95 End If
9697 p.Norm = Common.CalcNormal(New Point3D () {p.P1, p.P2, p.P3, p.P4})
9899 p.RealColour = GetColorArray(p.colour , curCol)
100 End Sub
101102 Private Shared Sub TransformSpecialLine(ByVal p As Part_SpecialLine , ByVal curCol As Integer)
103 p.LineP1 = TransformPoint(p.LineP1)
104 p.LineP2 = TransformPoint(p.LineP2)
105 p.TestP1 = TransformPoint(p.TestP1)
106 p.TestP2 = TransformPoint(p.TestP2)
107108 p.RealColour = GetColorArray(p.colour , curCol)
109 End Sub
110111 Private Shared Function TransformPoint(ByVal P As Point3D) As Point3D
112 Dim arr() As Object = TransformMatrixStack.ToArray
113 Dim tmpP As New Point3D(P.x, P.y, P.z)
114 Dim oldP As Point3D = tmpP.Copy
115 For i As Integer = 0 To arr.Length - 1 ’ arr.Length - 2 To 0 Step -1
116 With DirectCast(arr(i), Matrix)
117 If .Identity Then
118 tmpP.x = oldP.x + .matrix(0, 3)
119 tmpP.y = oldP.y + .matrix(1, 3)
120 tmpP.z = oldP.z + .matrix(2, 3)
121 Else
122 tmpP.x = .matrix(0, 0) * oldP.x + .matrix(0, 1) * oldP.y + .matrix(0, 2) * oldP.z + .matrix(0, 3)
123 tmpP.y = .matrix(1, 0) * oldP.x + .matrix(1, 1) * oldP.y + .matrix(1, 2) * oldP.z + .matrix(1, 3)
124 tmpP.z = .matrix(2, 0) * oldP.x + .matrix(2, 1) * oldP.y + .matrix(2, 2) * oldP.z + .matrix(2, 3)
125 End If
126 End With
127 oldP = tmpP.Copy ’New Point3D(tmpP.x, tmpP.y, tmpP.z) ’Else clones object if do oldP = tmpP
128 Next
129 Return oldP
130 End Function
131132 Private Shared Function GetColorArray(ByVal Col As Integer , ByVal curCol As Integer) As Single ()
133 Dim c As Color = SortColor(Col , curCol)
134 Return New Single () {CSng(c.R / 255), CSng(c.G / 255), CSng(c.B / 255), CSng(c.A / 255)}
135 End Function
136137 Private Shared Function SortColor(ByVal col As Integer , ByVal curCol As Integer) As Color
138 ’Sorts out the colour codes
139140 ’Colours 0 to 15 represent colours as defined by the old style quick basic
141 ’colour codes - would use the built in functionality but it has , at least ,
142 ’blue and red swapped!
143144 Select Case col
145146 Case 0
147 Return Color.Black
148 Case 1
149 Return Color.DarkBlue
150 Case 2
151 Return Color.DarkGreen
152 Case 3
153 Return Color.DarkCyan
154 Case 4
155 Return Color.DarkRed
156 Case 5
157 Return Color.DarkMagenta
158 Case 6
159 Return Color.Brown
160 Case 7
161 Return Color.Gray
162 Case 8
163 Return Color.DarkGray
164 Case 9
165 Return Color.Blue
166 Case 10
167 Return Color.Green
168 Case 11
169 Return Color.Cyan
170 Case 12
171 Return Color.Red
172 Case 13
173 Return Color.Pink
174 Case 14
175 Return Color.Yellow
176 Case 15
177 Return Color.White
178 Case 16
179 ’16 stands for the current colour - i.e that used by the last line
180 Return SortColor(curCol , curCol)
181 Case 24
182 ’Reverse of the current colour
183 Dim chkCol As Integer = curCol
92
184185 If curCol >= 32 And curCol <= 47 Then ’transparent colours - col + 32
186 chkCol = curCol - 32
187 End If
188189 If curCol >= 256 And curCol <= 511 Then ’dithered colours
190 ’colour = (J * 16) + K + 256
191 ’Use J to for the complementary colour
192193 chkCol = chkCol - 256
194 Dim J As Integer = CInt(Int(chkCol / 16)) ’will give the rounded down number
195 Dim K As Integer = chkCol - (J * 16)
196197 chkCol = J
198 End If
199200 Return SortColor(GetReversedCol(chkCol), curCol)
201202203 Case 72 ’Extra New Colours Below Until the End Select statement
204 Return Color.FromArgb (99, 95, 97)
205 Case 28
206 Return Color.FromArgb (197, 151, 80)
207 Case 17
208 Return Color.FromArgb (186, 255, 206)
209 Case 20
210 Return Color.FromArgb (215, 196, 230)
211 Case 18
212 Return Color.FromArgb (253, 232, 150)
213 Case 27
214 Return Color.FromArgb (215, 240, 0)
215 Case 26
216 Return Color.FromArgb (216, 27, 109)
217 Case 25
218 Return Color.FromArgb (249, 96, 0)
219 Case 134
220 Return Color.FromArgb (147, 135, 103)
221 Case 142
222 Return Color.FromArgb (215, 169, 75)
223 Case 135
224 Return Color.FromArgb (171, 173, 172)
225 Case 137
226 Return Color.FromArgb (106, 122, 150)
227 Case 21
228 Return Color.FromArgb (224, 255, 176)
229 Case 70
230 Return Color.FromArgb (105, 64, 39)
231 Case 71
232 Return Color.FromArgb (163, 162, 164)
233 Case 19
234 Return Color.FromArgb (232, 207, 161)
235 Case 57
236 Return Color.FromArgb (50, 249, 96, 0)
237 Case 22
238 Return Color.FromArgb (129, 0, 123)
239 Case 23
240 Return Color.FromArgb (71, 50, 176)
241242 End Select
243244 If col >= 32 And col <= 47 Then ’(32 + 15) ’32 for transparancy
245 Return Color.FromArgb (128, SortColor(col - 32, curCol )) ’Sets transparancy to 50%
246 End If
247248 If col >= 256 And col <= 511 Then
249 ’Dithered Colours!
250 col = col - 256
251 Dim J As Integer = CInt(Int(col / 16)) ’will give the rounded down number
252 Dim K As Integer = col - (J * 16)
253254 ’mixes the two colour values (rather than using dithered patterns which was used when only 16
255 ’colour displays were available
256257 Dim tmpcol As Color = SortColor(J, curCol)
258 Dim tmpcol2 As Color = SortColor(K, curCol)
259 Return Color.FromArgb(CInt(( tmpcol.R + tmpcol2.R) / 2), CInt(( tmpcol.G + tmpcol2.G) / 2), _
260 CInt(( tmpcol.B + tmpcol2.B) / 2))
261 End If
262263264 End Function
265266 Private Shared Function GetReversedCol(ByVal Col As Integer) As Integer
267 Select Case Col
268 Case 0, 7, 14, 15
269 Return 8
270 Case 1, 2, 3, 4, 5
271 Return Col + 8
272 Case 6, 8
273 Return 0
274 Case 9, 10, 11, 12, 13
275 Return Col - 8
276 Case Else ’For any undefined colour (i.e. the extended colours use darkend colour (i.e. mix with black)
277 Return 7
278 End Select
93
279 End Function
280 End Class
94