model-converter-python/d3/model/basemodel.py

288 lines
9.0 KiB
Python
Raw Normal View History

2016-11-22 15:13:35 +01:00
from math import sqrt
2016-11-25 14:28:39 +01:00
from ..geometry import Vector
from .mesh import Material, MeshPart
2016-11-25 14:28:39 +01:00
Vertex = Vector
2016-11-21 15:59:03 +01:00
Normal = Vertex
TexCoord = Vertex
class FaceVertex:
2016-11-30 12:11:31 +01:00
"""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.
"""
2016-11-29 12:13:43 +01:00
def __init__(self, vertex = None, tex_coord = None, normal = None):
2016-11-30 12:11:31 +01:00
"""Initializes a FaceVertex from its indices
"""
2016-11-21 15:59:03 +01:00
self.vertex = vertex
2016-11-29 12:13:43 +01:00
self.tex_coord = tex_coord
2016-11-21 15:59:03 +01:00
self.normal = normal
def from_array(self, arr):
2016-11-30 12:11:31 +01:00
"""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
2016-11-23 16:36:32 +01:00
try:
2016-11-29 12:13:43 +01:00
self.tex_coord = int(arr[1]) if len(arr) > 1 else None
2016-11-23 16:36:32 +01:00
except:
2016-11-29 12:13:43 +01:00
self.tex_coord = None
2016-11-23 16:36:32 +01:00
try:
self.normal = int(arr[2]) if len(arr) > 2 else None
except:
self.normal = None
2016-11-21 15:59:03 +01:00
return self
class Face:
2016-11-30 12:11:31 +01:00
"""Represents a face with 3 vertices
2016-12-02 16:04:05 +01:00
Faces with more than 3 vertices are not supported in this class. You should
split your face first and then create the number needed of instances of
this class.
2016-11-30 12:11:31 +01:00
"""
2016-11-29 12:13:43 +01:00
def __init__(self, a = None, b = None, c = None, material = None):
2016-11-30 12:11:31 +01:00
"""Initializes a Face with its three FaceVertex and its Material
"""
2016-11-21 15:59:03 +01:00
self.a = a
self.b = b
self.c = c
2016-11-29 12:13:43 +01:00
self.material = material
2016-11-21 15:59:03 +01:00
# Expects array of array
def from_array(self, arr):
2016-11-30 12:11:31 +01:00
"""Initializes a Face with an array
The array should be an array of array of objects.
Each array will represent a FaceVertex
"""
2016-11-21 15:59:03 +01:00
self.a = FaceVertex().from_array(arr[0])
self.b = FaceVertex().from_array(arr[1])
self.c = FaceVertex().from_array(arr[2])
return self
class ModelParser:
2016-11-30 12:11:31 +01:00
"""Represents a 3D model
"""
2016-11-30 15:37:58 +01:00
def __init__(self, up_conversion = None):
2016-11-30 12:11:31 +01:00
"""Initializes the model
"""
2016-11-30 15:37:58 +01:00
self.up_conversion = up_conversion
2016-11-21 15:59:03 +01:00
self.vertices = []
self.normals = []
self.tex_coords = []
self.parts = []
self.current_part = None
2016-11-22 12:09:54 +01:00
self.bounding_box = BoundingBox()
self.center_and_scale = True
self.vertex_vbo = None
self.tex_coord_vbo = None
self.normal_vbo = None
2016-11-29 12:05:03 +01:00
self.path = None
2016-11-21 15:59:03 +01:00
def init_textures(self):
2016-11-30 12:11:31 +01:00
"""Initializes the textures of the parts of the model
Basically, calls glGenTexture on each texture
"""
for part in self.parts:
part.init_texture()
2016-11-21 15:59:03 +01:00
def add_vertex(self, vertex):
2016-11-30 12:11:31 +01:00
"""Adds a vertex to the current model
2016-12-02 16:01:42 +01:00
Will also update its bounding box, and convert the up vector if
up_conversion was specified.
2016-11-30 12:11:31 +01:00
"""
2016-11-30 15:37:58 +01:00
# Apply up_conversion to the vertex
new_vertex = vertex
if self.up_conversion is not None:
if self.up_conversion[0] == 'y' and self.up_conversion[1] == 'z':
new_vertex = Vector(vertex.y, vertex.z, vertex.x)
elif self.up_conversion[0] == 'z' and self.up_conversion[1] == 'y':
new_vertex = Vector(vertex.z, vertex.x, vertex.y)
self.vertices.append(new_vertex)
self.bounding_box.add(new_vertex)
2016-11-21 15:59:03 +01:00
def add_tex_coord(self, tex_coord):
2016-11-30 12:11:31 +01:00
"""Adds a texture coordinate element to the current model
"""
2016-11-21 15:59:03 +01:00
self.tex_coords.append(tex_coord)
def add_normal(self, normal):
2016-11-30 12:11:31 +01:00
"""Adds a normal element to the current model
"""
2016-11-21 15:59:03 +01:00
self.normals.append(normal)
2016-11-21 16:18:49 +01:00
def add_face(self, face):
2016-11-30 12:11:31 +01:00
"""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.
"""
2016-11-30 14:57:58 +01:00
if self.current_part is None or (face.material != self.current_part.material and face.material is not None):
self.current_part = MeshPart(self)
2016-11-30 10:08:37 +01:00
self.current_part.material = face.material if face.material is not None else Material.DEFAULT_MATERIAL
self.parts.append(self.current_part)
self.current_part.add_face(face)
2016-11-21 15:59:03 +01:00
def parse_line(self, string):
2016-11-30 12:11:31 +01:00
"""Parses a line of a text model
This method needs to be implemented by each subclass of ModelParser.
"""
2016-11-21 15:59:03 +01:00
pass
def parse_file(self, path):
2016-11-30 12:11:31 +01:00
"""Sets the path of the model and parse each line
"""
2016-11-29 12:05:03 +01:00
self.path = path
2016-11-21 15:59:03 +01:00
with open(path) as f:
for line in f.readlines():
line = line.rstrip()
2017-01-13 09:43:24 +01:00
if line != '':
self.parse_line(line)
2016-11-21 15:59:03 +01:00
2016-11-25 17:08:57 +01:00
def draw(self):
2016-11-30 12:11:31 +01:00
"""Draws each part of the model with OpenGL
"""
import OpenGL.GL as gl
2016-11-22 12:09:54 +01:00
if self.center_and_scale:
center = self.bounding_box.get_center()
scale = self.bounding_box.get_scale() / 2
gl.glPushMatrix()
gl.glScalef(1/scale, 1/scale, 1/scale)
gl.glTranslatef(-center.x, -center.y, -center.z)
for part in self.parts:
part.draw()
2016-11-22 12:09:54 +01:00
if self.center_and_scale:
gl.glPopMatrix()
def generate_vbos(self):
2016-11-30 12:11:31 +01:00
"""Generates the VBOs of each part of the model
"""
for part in self.parts:
part.generate_vbos()
2016-11-23 12:07:47 +01:00
2016-11-22 15:13:35 +01:00
def generate_vertex_normals(self):
2016-11-30 12:11:31 +01:00
"""Generate the normals for each vertex of the model
2016-11-30 12:11:31 +01:00
A normal will be the average normal of the adjacent faces of a vertex.
"""
2016-11-22 15:13:35 +01:00
self.normals = [Normal() for i in self.vertices]
for part in self.parts:
for face in part.faces:
v1 = Vertex.from_points(self.vertices[face.a.vertex], self.vertices[face.b.vertex])
v2 = Vertex.from_points(self.vertices[face.a.vertex], self.vertices[face.c.vertex])
cross = Vertex.cross_product(v1, v2)
self.normals[face.a.vertex] += cross
self.normals[face.b.vertex] += cross
self.normals[face.c.vertex] += cross
2016-11-22 15:13:35 +01:00
for normal in self.normals:
normal.normalize()
for part in self.parts:
for face in part.faces:
face.a.normal = face.a.vertex
face.b.normal = face.b.vertex
face.c.normal = face.c.vertex
2016-11-22 15:13:35 +01:00
def generate_face_normals(self):
2016-11-30 12:11:31 +01:00
"""Generate the normals for each face of the model
2016-11-22 15:13:35 +01:00
2016-11-30 12:11:31 +01:00
A normal will be the normal of the face
"""
2016-11-30 10:08:37 +01:00
# Build array of faces
faces = sum(map(lambda x: x.faces, self.parts), [])
self.normals = [Normal()] * len(faces)
2016-11-22 15:13:35 +01:00
2016-11-30 10:08:37 +01:00
for (index, face) in enumerate(faces):
2016-11-22 15:13:35 +01:00
v1 = Vertex.from_points(self.vertices[face.a.vertex], self.vertices[face.b.vertex])
v2 = Vertex.from_points(self.vertices[face.a.vertex], self.vertices[face.c.vertex])
cross = Vertex.cross_product(v1, v2)
cross.normalize()
self.normals[index] = cross
face.a.normal = index
face.b.normal = index
face.c.normal = index
2016-11-22 12:09:54 +01:00
class BoundingBox:
2016-11-30 12:11:31 +01:00
"""Represents a bounding box of a 3D model
"""
2016-11-22 12:09:54 +01:00
def __init__(self):
2016-11-30 12:11:31 +01:00
"""Initializes the coordinates of the bounding box
"""
2016-11-22 12:09:54 +01:00
self.min_x = +float('inf')
self.min_y = +float('inf')
self.min_z = +float('inf')
self.max_x = -float('inf')
self.max_y = -float('inf')
self.max_z = -float('inf')
2016-11-30 12:11:31 +01:00
def add(self, vector):
"""Adds a vector to a bounding box
If the vector is outside the bounding box, the bounding box will be
enlarged, otherwise, nothing will happen.
"""
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)
2016-11-22 12:09:54 +01:00
2016-11-30 12:11:31 +01:00
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)
2016-11-22 12:09:54 +01:00
def __str__(self):
2016-11-30 12:11:31 +01:00
"""Returns a string that represents the bounding box
"""
2016-11-22 12:09:54 +01:00
return "[{},{}],[{},{}],[{},{}]".format(
self.min_x,
self.min_y,
self.min_z,
self.max_x,
self.max_y,
self.max_z)
def get_center(self):
2016-11-30 12:11:31 +01:00
"""Returns the center of the bounding box
"""
2016-11-22 12:09:54 +01:00
return Vertex(
(self.min_x + self.max_x) / 2,
(self.min_y + self.max_y) / 2,
(self.min_z + self.max_z) / 2)
def get_scale(self):
2016-11-30 12:11:31 +01:00
"""Returns the maximum edge of the bounding box
"""
2016-11-22 12:09:54 +01:00
return max(
abs(self.max_x - self.min_x),
abs(self.max_y - self.min_y),
abs(self.max_z - self.min_z))
2016-11-21 15:59:03 +01:00
class Exporter:
2016-11-30 12:11:31 +01:00
"""Represents an object that can export a model into a certain format
"""
2016-11-21 15:59:03 +01:00
def __init__(self, model):
self.model = model
2016-11-22 11:27:42 +01:00