This commit is contained in:
Thomas FORGIONE 2016-11-30 12:11:31 +01:00
parent 9b76190292
commit ae3476d719
No known key found for this signature in database
GPG Key ID: 2A210FFC062E00C3
1 changed files with 83 additions and 8 deletions

View File

@ -11,12 +11,25 @@ Normal = Vertex
TexCoord = Vertex TexCoord = Vertex
class FaceVertex: class FaceVertex:
"""Contains the information a vertex needs in a face
In contains the index of the vertex, the index of the texture coordinate
and the index of the normal. It is None if it is not available.
"""
def __init__(self, vertex = None, tex_coord = None, normal = None): def __init__(self, vertex = None, tex_coord = None, normal = None):
"""Initializes a FaceVertex from its indices
"""
self.vertex = vertex self.vertex = vertex
self.tex_coord = tex_coord self.tex_coord = tex_coord
self.normal = normal self.normal = normal
def from_array(self, arr): def from_array(self, arr):
"""Initializes a FaceVertex from an array
The array can be an array of strings, the first value will be the
vertex index, the second will be the texture coordinate index, and the
third will be the normal index.
"""
self.vertex = int(arr[0]) if len(arr) > 0 else None self.vertex = int(arr[0]) if len(arr) > 0 else None
try: try:
@ -32,7 +45,11 @@ class FaceVertex:
return self return self
class Face: class Face:
"""Represents a face with 3 vertices
"""
def __init__(self, a = None, b = None, c = None, material = None): def __init__(self, a = None, b = None, c = None, material = None):
"""Initializes a Face with its three FaceVertex and its Material
"""
self.a = a self.a = a
self.b = b self.b = b
self.c = c self.c = c
@ -40,14 +57,22 @@ class Face:
# Expects array of array # Expects array of array
def from_array(self, arr): def from_array(self, arr):
"""Initializes a Face with an array
The array should be an array of array of objects.
Each array will represent a FaceVertex
"""
self.a = FaceVertex().from_array(arr[0]) self.a = FaceVertex().from_array(arr[0])
self.b = FaceVertex().from_array(arr[1]) self.b = FaceVertex().from_array(arr[1])
self.c = FaceVertex().from_array(arr[2]) self.c = FaceVertex().from_array(arr[2])
return self return self
class ModelParser: class ModelParser:
"""Represents a 3D model
"""
def __init__(self): def __init__(self):
"""Initializes the model
"""
self.vertices = [] self.vertices = []
self.normals = [] self.normals = []
self.tex_coords = [] self.tex_coords = []
@ -61,20 +86,37 @@ class ModelParser:
self.path = None self.path = None
def init_textures(self): def init_textures(self):
"""Initializes the textures of the parts of the model
Basically, calls glGenTexture on each texture
"""
for part in self.parts: for part in self.parts:
part.init_texture() part.init_texture()
def add_vertex(self, vertex): def add_vertex(self, vertex):
"""Adds a vertex to the current model
Will also update its bounding box
"""
self.vertices.append(vertex) self.vertices.append(vertex)
self.bounding_box.add(vertex) self.bounding_box.add(vertex)
def add_tex_coord(self, tex_coord): def add_tex_coord(self, tex_coord):
"""Adds a texture coordinate element to the current model
"""
self.tex_coords.append(tex_coord) self.tex_coords.append(tex_coord)
def add_normal(self, normal): def add_normal(self, normal):
"""Adds a normal element to the current model
"""
self.normals.append(normal) self.normals.append(normal)
def add_face(self, face): def add_face(self, face):
"""Adds a face to the current model
If the face has a different material than the current material, it will
create a new mesh part and update the current material.
"""
if self.current_part is None or face.material != self.current_part.material: if self.current_part is None or face.material != self.current_part.material:
self.current_part = MeshPart(self) self.current_part = MeshPart(self)
self.current_part.material = face.material if face.material is not None else Material.DEFAULT_MATERIAL self.current_part.material = face.material if face.material is not None else Material.DEFAULT_MATERIAL
@ -83,9 +125,15 @@ class ModelParser:
self.current_part.add_face(face) self.current_part.add_face(face)
def parse_line(self, string): def parse_line(self, string):
"""Parses a line of a text model
This method needs to be implemented by each subclass of ModelParser.
"""
pass pass
def parse_file(self, path): def parse_file(self, path):
"""Sets the path of the model and parse each line
"""
self.path = path self.path = path
with open(path) as f: with open(path) as f:
for line in f.readlines(): for line in f.readlines():
@ -93,6 +141,8 @@ class ModelParser:
self.parse_line(line) self.parse_line(line)
def draw(self): def draw(self):
"""Draws each part of the model with OpenGL
"""
import OpenGL.GL as gl import OpenGL.GL as gl
if self.center_and_scale: if self.center_and_scale:
@ -109,11 +159,16 @@ class ModelParser:
gl.glPopMatrix() gl.glPopMatrix()
def generate_vbos(self): def generate_vbos(self):
"""Generates the VBOs of each part of the model
"""
for part in self.parts: for part in self.parts:
part.generate_vbos() part.generate_vbos()
def generate_vertex_normals(self): def generate_vertex_normals(self):
"""Generate the normals for each vertex of the model
A normal will be the average normal of the adjacent faces of a vertex.
"""
self.normals = [Normal() for i in self.vertices] self.normals = [Normal() for i in self.vertices]
for part in self.parts: for part in self.parts:
@ -135,7 +190,10 @@ class ModelParser:
face.c.normal = face.c.vertex face.c.normal = face.c.vertex
def generate_face_normals(self): def generate_face_normals(self):
"""Generate the normals for each face of the model
A normal will be the normal of the face
"""
# Build array of faces # Build array of faces
faces = sum(map(lambda x: x.faces, self.parts), []) faces = sum(map(lambda x: x.faces, self.parts), [])
self.normals = [Normal()] * len(faces) self.normals = [Normal()] * len(faces)
@ -154,7 +212,11 @@ class ModelParser:
class BoundingBox: class BoundingBox:
"""Represents a bounding box of a 3D model
"""
def __init__(self): def __init__(self):
"""Initializes the coordinates of the bounding box
"""
self.min_x = +float('inf') self.min_x = +float('inf')
self.min_y = +float('inf') self.min_y = +float('inf')
self.min_z = +float('inf') self.min_z = +float('inf')
@ -163,16 +225,23 @@ class BoundingBox:
self.max_y = -float('inf') self.max_y = -float('inf')
self.max_z = -float('inf') self.max_z = -float('inf')
def add(self, vertex): def add(self, vector):
self.min_x = min(self.min_x, vertex.x) """Adds a vector to a bounding box
self.min_y = min(self.min_y, vertex.y)
self.min_z = min(self.min_z, vertex.z)
self.max_x = max(self.max_x, vertex.x) If the vector is outside the bounding box, the bounding box will be
self.max_y = max(self.max_y, vertex.y) enlarged, otherwise, nothing will happen.
self.max_z = max(self.max_z, vertex.z) """
self.min_x = min(self.min_x, vector.x)
self.min_y = min(self.min_y, vector.y)
self.min_z = min(self.min_z, vector.z)
self.max_x = max(self.max_x, vector.x)
self.max_y = max(self.max_y, vector.y)
self.max_z = max(self.max_z, vector.z)
def __str__(self): def __str__(self):
"""Returns a string that represents the bounding box
"""
return "[{},{}],[{},{}],[{},{}]".format( return "[{},{}],[{},{}],[{},{}]".format(
self.min_x, self.min_x,
self.min_y, self.min_y,
@ -182,12 +251,16 @@ class BoundingBox:
self.max_z) self.max_z)
def get_center(self): def get_center(self):
"""Returns the center of the bounding box
"""
return Vertex( return Vertex(
(self.min_x + self.max_x) / 2, (self.min_x + self.max_x) / 2,
(self.min_y + self.max_y) / 2, (self.min_y + self.max_y) / 2,
(self.min_z + self.max_z) / 2) (self.min_z + self.max_z) / 2)
def get_scale(self): def get_scale(self):
"""Returns the maximum edge of the bounding box
"""
return max( return max(
abs(self.max_x - self.min_x), abs(self.max_x - self.min_x),
abs(self.max_y - self.min_y), abs(self.max_y - self.min_y),
@ -195,6 +268,8 @@ class BoundingBox:
class Exporter: class Exporter:
"""Represents an object that can export a model into a certain format
"""
def __init__(self, model): def __init__(self, model):
self.model = model self.model = model