-
Notifications
You must be signed in to change notification settings - Fork 8
Expand file tree
/
Copy pathparse_obj.lua
More file actions
131 lines (105 loc) · 4.22 KB
/
parse_obj.lua
File metadata and controls
131 lines (105 loc) · 4.22 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
--[[
Copyright (C) 2021 random-geek (https://github.com/random-geek)
This file is part of Meshport.
Meshport is free software: you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the Free
Software Foundation, either version 3 of the License, or (at your option)
any later version.
Meshport is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
more details.
You should have received a copy of the GNU Lesser General Public License
along with Meshport. If not, see <https://www.gnu.org/licenses/>.
]]
-- See the OBJ file specification: http://www.martinreddy.net/gfx/3d/OBJ.spec
-- Also, the Irrlicht implementation: irrlicht/source/Irrlicht/COBJMeshFileLoader.cpp
local function parse_vector_element(elementType, elementStr)
if elementType == "v" or elementType == "vn" then
-- Note that there may be an optional weight value after z, which is ignored.
local xs, ys, zs = string.match(elementStr, "^([%d%.%-]+)%s+([%d%.%-]+)%s+([%d%.%-]+)")
-- The X axis of vectors is inverted to match the Luanti coordinate system.
local vec = vector.new(-tonumber(xs), tonumber(ys), tonumber(zs))
if elementType == "v" then
return "verts", vec
else
return "vert_norms", vec
end
elseif elementType == "vt" then
local xs, ys = string.match(elementStr, "^([%d%.%-]+)%s+([%d%.%-]+)")
local coords = {x = tonumber(xs), y = tonumber(ys)}
assert(coords.x and coords.y, "Invalid texture coordinate element")
return "tex_coords", coords
end
end
local function parse_face_element(elements, faceStr)
-- Split the face element into strings containing the indices of elements associated with each vertex.
local vertStrs = string.split(faceStr, " ")
local face = {
verts = {},
tex_coords = {},
vert_norms = {},
}
for i, vertStr in ipairs(vertStrs) do
-- Split the string into indices for vertex, texture coordinate, and/or vertex normal elements.
local vs, vts, vns = string.match(vertStr, "^(%d*)/?(%d*)/?(%d*)$")
local vi, vti, vni = tonumber(vs), tonumber(vts), tonumber(vns)
assert(vi, "Invalid face element")
-- Set the position, texture coordinate, and vertex normal of the vertex.
-- Note that vti or vni are allowed to be nil
face.verts[i] = elements.verts[vi]
face.tex_coords[i] = elements.tex_coords[vti]
face.vert_norms[i] = elements.vert_norms[vni]
end
return face
end
local function handle_group(groups, elementStr)
-- Note: Luanti ignores usemtl; see `OBJ_LOADER_IGNORE_MATERIAL_FILES`.
-- The format allows multiple group names; get only the first one.
local groupName = string.match(elementStr, "^(%S+)")
if not groupName then
-- "default" is the default group name if no name is specified.
groupName = "default"
end
local groupIdx = table.indexof(groups, groupName)
-- If this group has not been used yet, add it to the list.
if groupIdx < 0 then
table.insert(groups, groupName)
groupIdx = #groups
end
return groupIdx
end
function meshport.parse_obj(path)
local file = io.open(path, "r")
local faces = meshport.Faces:new()
local elements = {
verts = {},
tex_coords = {},
vert_norms = {},
}
-- Tiles are assigned according to groups, in the order in which groups are defined.
local groups = {}
local currentTileIdx
for line in file:lines() do
-- elementStr may be an empty string, e.g. "g" with no group name.
local elementType, elementStr = string.match(line, "^(%a+)%s*(.*)")
if elementType == "v" or elementType == "vt" or elementType == "vn" then
local dest, value = parse_vector_element(elementType, elementStr)
table.insert(elements[dest], value)
elseif elementType == "f" then
-- If the face is not part of any group, use the placeholder group `0`.
if not currentTileIdx then
table.insert(groups, 0)
currentTileIdx = #groups
end
-- Parse the face element.
local face = parse_face_element(elements, elementStr)
-- Assign materials according to the group.
face.tile_idx = currentTileIdx
faces:insert_face(face)
elseif elementType == "g" then
currentTileIdx = handle_group(groups, elementStr)
end
end
return faces
end