Skip to content

Commit 59c4a8c

Browse files
authored
Merge pull request #94 from Janjre/master
3D model to Minecraft!
2 parents 48eecb4 + 2e58a02 commit 59c4a8c

2 files changed

Lines changed: 641 additions & 0 deletions

File tree

Libs/geometryLib.lua

Lines changed: 385 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,385 @@
1+
function cross_product(v1, v2)
2+
return {x = v1.y * v2.z - v1.z * v2.y, y = v1.z * v2.x - v1.x * v2.z, z = v1.x * v2.y - v1.y * v2.x}
3+
end
4+
5+
function dot_product(v1, v2)
6+
return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z
7+
end
8+
9+
function l2norm(v)
10+
return math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z)
11+
end
12+
13+
function subtract_vectors(v1, v2)
14+
if v1.z == nil and v2.z==nil then
15+
return {x = v1.x - v2.x, y = v1.y - v2.y}
16+
else
17+
return {x= v1.x - v2.x, y=v1.y - v2.y, z=v1.z - v2.z}
18+
end
19+
end
20+
21+
function add_vectors(v1, v2)
22+
if v1.z == nil and v2.z==nil then
23+
return { x = v1.x + v2.x, y = v1.y + v2.y}
24+
else
25+
return { x = v1.x + v2.x, y = v1.y + v2.y, z = v1.z + v2.z }
26+
end
27+
end
28+
29+
function scale_vector(v, scale)
30+
if v.z == nil then
31+
return { x = v.x * scale, y = v.y * scale}
32+
else
33+
return { x = v.x * scale, y = v.y * scale, z = v.z * scale }
34+
end
35+
end
36+
37+
function get_triangle_edges(triangle)
38+
local edges = {}
39+
table.insert(edges, {x = triangle.v2.x - triangle.v1.x, y = triangle.v2.y - triangle.v1.y, z = triangle.v2.z - triangle.v1.z})
40+
table.insert(edges, {x = triangle.v3.x - triangle.v2.x, y = triangle.v3.y - triangle.v2.y, z = triangle.v3.z - triangle.v2.z})
41+
table.insert(edges, {x = triangle.v1.x - triangle.v3.x, y = triangle.v1.y - triangle.v3.y, z = triangle.v1.z - triangle.v3.z})
42+
return edges
43+
end
44+
45+
local function get_triangle_normals(triangle)
46+
local triangle_edges = get_triangle_edges(triangle)
47+
return {cross_product(triangle_edges[1], triangle_edges[2])}
48+
end
49+
50+
51+
function scale_and_move_verts(verts, minBound, modelToVoxelRatio)
52+
local x,y,z
53+
local uniqueVoxels = {}
54+
local uniqueCount = 0
55+
56+
-- for each vert in the model
57+
for i, vert in ipairs(verts) do
58+
-- we don't want negative values, so translate the vert so that the smallest value on each axis is at 0,0,0
59+
x = vert.x + -minBound.x
60+
y = vert.y + -minBound.y
61+
z = vert.z + -minBound.z
62+
63+
-- scale the vert position so that the largest value on the largest axis fits within our desired voxel model size
64+
x = x * modelToVoxelRatio
65+
y = y * modelToVoxelRatio
66+
z = z * modelToVoxelRatio
67+
verts[i] = {x=x, y=y, z=z}
68+
--print("verts["..i.."] x: ", x, "y: ", y, "z: ", z)
69+
end
70+
return verts
71+
end
72+
73+
74+
local function printTable(t)
75+
if type(t) ~= "table" then
76+
print(t)
77+
return
78+
end
79+
for key, value in pairs(t) do
80+
if type(value) == "table" then
81+
print(key .. ":")
82+
printTable(value)
83+
else
84+
print(key .. " = " .. tostring(value))
85+
end
86+
end
87+
end
88+
89+
90+
function test_AABB_triangle_intersection(triangle, AABB_min)
91+
local function separating_axis_theorem(AABB_min, AABB_max, triangle)
92+
local function get_AABB_axis()
93+
local x_axis = {x = 1, y = 0, z = 0}
94+
local y_axis = {x = 0, y = 1, z = 0}
95+
local z_axis = {x = 0, y = 0, z = 1}
96+
return {x_axis, y_axis, z_axis}
97+
end
98+
99+
local function get_cross_products(axes1, axes2)
100+
local cross_products = {}
101+
for i, axis1 in ipairs(axes1) do
102+
for j, axis2 in ipairs(axes2) do
103+
table.insert(cross_products, cross_product(axis1, axis2))
104+
end
105+
end
106+
return cross_products
107+
end
108+
109+
local function project_points(points, axis)
110+
local ret = {}
111+
for i = 1, #points do
112+
ret[i] = dot_product(points[i], axis)
113+
end
114+
return ret
115+
end
116+
117+
local function overlap_intervals(interval1, interval2)
118+
return interval1[1] <= interval2[2] and interval2[1] <= interval1[2]
119+
end
120+
121+
local AABB_axes = get_AABB_axis()
122+
local triangle_edges = get_triangle_edges(triangle)
123+
local triangle_normals = get_triangle_normals(triangle)
124+
local cross_products = get_cross_products(AABB_axes, triangle_edges)
125+
126+
local axes_to_test = {}
127+
for _, axes in pairs(AABB_axes) do
128+
table.insert(axes_to_test, axes)
129+
end
130+
for _, axes in pairs(triangle_normals) do
131+
table.insert(axes_to_test, axes)
132+
end
133+
for _, axes in pairs(cross_products) do
134+
table.insert(axes_to_test, axes)
135+
end
136+
137+
AABB_points = {
138+
{x = AABB_min.x, y = AABB_min.y, z = AABB_min.z},--0
139+
{x = AABB_max.x, y = AABB_min.y, z = AABB_min.z},--1
140+
{x = AABB_min.x, y = AABB_max.y, z = AABB_min.z},--2
141+
{x = AABB_min.x, y = AABB_min.y, z = AABB_max.z},--3
142+
{x = AABB_max.x, y = AABB_max.y, z = AABB_min.z},--4
143+
{x = AABB_max.x, y = AABB_min.y, z = AABB_max.z},--5
144+
{x = AABB_min.x, y = AABB_max.y, z = AABB_max.z},--6
145+
{x = AABB_max.x, y = AABB_max.y, z = AABB_max.z}--7
146+
}
147+
148+
for i, axis in ipairs(axes_to_test) do
149+
local mag = l2norm(axis)
150+
if mag < 1e-6 then
151+
--print("Axis is zero")
152+
else
153+
local scaled_axis = {x=axis.x/mag , y=axis.y / mag, z=axis.z/mag}
154+
155+
local AABB_proj = project_points(AABB_points, scaled_axis)
156+
157+
local triangle_proj = project_points({triangle.v1, triangle.v2, triangle.v3}, scaled_axis)
158+
159+
local AABB_interval = {math.min(table.unpack(AABB_proj)), math.max(table.unpack(AABB_proj))}
160+
local triangle_interval = {math.min(table.unpack(triangle_proj)), math.max(table.unpack(triangle_proj))}
161+
if not overlap_intervals(AABB_interval, triangle_interval) then
162+
return false
163+
end
164+
end
165+
end
166+
167+
return true
168+
end
169+
170+
local AABB_max = {x = AABB_min.x + 1, y = AABB_min.y + 1, z = AABB_min.z + 1}
171+
--print("AABB_min: ", AABB_min.x, AABB_min.y, AABB_min.z)
172+
--print("AABB_max: ", AABB_max.x, AABB_max.y, AABB_max.z)
173+
return separating_axis_theorem(AABB_min, AABB_max, triangle)
174+
175+
end
176+
177+
function barycentric_positions(triangle, point)
178+
-- see https://www.geogebra.org/m/ZuvmPjmy
179+
local v2 = subtract_vectors(point, triangle.v1)
180+
-- printTable(v2)
181+
local triangle_edges = {}
182+
triangle_edges[1] = subtract_vectors(triangle.v3, triangle.v1)
183+
triangle_edges[2] = subtract_vectors(triangle.v2, triangle.v1)
184+
-- Compute dot products
185+
local d00 = dot_product(triangle_edges[1], triangle_edges[1])
186+
local d01 = dot_product(triangle_edges[1], triangle_edges[2])
187+
local d11 = dot_product(triangle_edges[2], triangle_edges[2])
188+
local d20 = dot_product(v2, triangle_edges[1])
189+
local d21 = dot_product(v2, triangle_edges[2])
190+
-- print("d00: " .. d00)
191+
-- print("d01: " .. d01)
192+
-- print("d11: " .. d11)
193+
-- print("d20: " .. d20)
194+
-- print("d21: " .. d21)
195+
-- Compute barycentric coordinates
196+
local denom = d00 * d11 - d01 * d01
197+
-- print(denom)
198+
-- print("Denom: ", denom)
199+
if denom == 0 then
200+
do return false end -- Degenerate triangle
201+
end
202+
local gamma = (d11 * d20 - d01 * d21) / denom
203+
local beta = (d00 * d21 - d01 * d20) / denom
204+
local alpha = 1 - gamma - beta
205+
return {alpha=alpha, beta=beta, gamma=gamma}
206+
end
207+
208+
function closest_point_in_triangle_3d(triangle, P)
209+
local A, B, C = triangle.v1, triangle.v2, triangle.v3
210+
local function project_point_on_line_segment(P, A, B)
211+
local AB = subtract_vectors(B, A)
212+
local AP = subtract_vectors(P, A)
213+
local t = dot_product(AP, AB) / dot_product(AB, AB)
214+
t = math.max(0, math.min(1, t))
215+
return {x=A.x + t * AB.x, y=A.y + t * AB.y, z = A.z + t * AB.z}
216+
end
217+
218+
local function is_point_in_triangle_3d (triangle, P)
219+
local bry_pos = barycentric_positions(triangle, P)
220+
-- check in alpha, beta and gamma are all positive
221+
if not bry_pos then
222+
do return false end
223+
end
224+
local u, v, w = bry_pos.alpha, bry_pos.beta, bry_pos.gamma
225+
return u >= 0 and v >= 0 and w >= 0
226+
end
227+
228+
local function closest_point_on_triangle_face(triangle, P)
229+
local normal = get_triangle_normals(triangle)[1]
230+
normal = scale_vector(normal, 1/l2norm(normal))
231+
local AP = subtract_vectors(P, triangle.v1)
232+
-- printTable(normal)
233+
local distance_to_plane = dot_product(AP, normal)
234+
-- print(distance_to_plane)
235+
-- printTable(P)
236+
local projection = subtract_vectors(P, scale_vector(normal, distance_to_plane))
237+
if is_point_in_triangle_3d(triangle, projection) then
238+
do return projection end
239+
end
240+
return nil
241+
end
242+
243+
-- Check if projection of P onto the triangle's plane is inside the triangle
244+
local point_on_face = closest_point_on_triangle_face(triangle, P)
245+
-- printTable(point_on_face)
246+
if point_on_face == nil then else
247+
-- print("Closest point is on face")
248+
return point_on_face
249+
end
250+
local closest_on_AB = project_point_on_line_segment(P, triangle.v1, triangle.v2)
251+
local closest_on_BC = project_point_on_line_segment(P, triangle.v2, triangle.v3)
252+
local closest_on_CA = project_point_on_line_segment(P, triangle.v3, triangle.v1)
253+
254+
local closest_points = {closest_on_AB, closest_on_BC, closest_on_CA}
255+
local distances = {}
256+
for pointCount = 1, #closest_points do
257+
local point = closest_points[pointCount]
258+
distances[#distances+1] = l2norm(subtract_vectors(P, point))
259+
end
260+
261+
local minDistance = math.huge
262+
local minDistanceIndex = nil
263+
for i = 1, #closest_points do
264+
if distances[i] < minDistance then
265+
minDistance = distances[i]
266+
minDistanceIndex = i
267+
end
268+
end
269+
return closest_points[minDistanceIndex]
270+
end
271+
272+
function getUVFromPoint(point, triVerts, triUVs)
273+
local vertA, vertB, vertC = triVerts["v1"], triVerts["v2"], triVerts["v3"] -- Get the three corners of the triangle
274+
local uvA, uvB, uvC = triUVs["v1"], triUVs["v2"], triUVs["v3"] -- Get the UV coordinates at each corner
275+
276+
local bry_pos = barycentric_positions(triVerts, point)
277+
278+
assert(bry_pos, "Point is not in triangle")
279+
-- print("alpha = " .. bry_pos.alpha, "beta = " .. bry_pos.beta, "gamma = " .. bry_pos.gamma, "sum = " .. bry_pos.alpha + bry_pos.beta + bry_pos.gamma)
280+
281+
local uvAlpha = scale_vector(uvA, bry_pos.alpha)
282+
local uvBeta = scale_vector(uvB, bry_pos.beta)
283+
local uvGamma = scale_vector(uvC, bry_pos.gamma)
284+
-- print("uvAlpha: ", uvAlpha.x, uvAlpha.y, uvAlpha.z)
285+
-- print("uvBeta: ", uvBeta.x, uvBeta.y, uvBeta.z)
286+
-- print("uvGamma: ", uvGamma.x, uvGamma.y, uvGamma.z)
287+
--printTable(add_vectors(add_vectors(uvAlpha, uvBeta), uvGamma))
288+
return add_vectors(add_vectors(uvAlpha, uvBeta), uvGamma)
289+
end
290+
291+
function getTextureColor(x, y, r_channel, g_channel, b_channel)
292+
-- print(x,y)
293+
x = math.min(math.floor(x * #r_channel[1])+1, #r_channel[1])
294+
y = math.min(math.floor(y * #r_channel[1])+1, #r_channel[1])
295+
-- print(x,y)
296+
local r = r_channel[y][x]
297+
local g = g_channel[y][x]
298+
local b = b_channel[y][x]
299+
return {r=r, g=g, b=b}
300+
end
301+
302+
303+
function coloured_voxelise_obj(verts, vertexTextures, faces, r_channel, g_channel, b_channel)
304+
-- helper function to make a unique key from x,y,z input
305+
local function makeVertexKey(x, y, z)
306+
return string.format("%d,%d,%d", x, y, z)
307+
end
308+
-- helper function to round a number
309+
local function rd(num)
310+
return math.floor(num)
311+
end
312+
local function ru(num)
313+
return math.ceil(num)
314+
end
315+
316+
local uniqueVoxels = {}
317+
local repeatVoxelsCount = 0
318+
local uniqueVoxelColors = {}
319+
local uniqueCount = 0
320+
321+
for i, face in ipairs(faces) do
322+
local v = face.v
323+
local t = face.t
324+
local n = face.n
325+
local triangle = {}
326+
triangle["v1"] = {x=verts[v[1]].x,y=verts[v[1]].y,z=verts[v[1]].z}
327+
triangle["v2"] = {x=verts[v[2]].x,y=verts[v[2]].y,z=verts[v[2]].z}
328+
triangle["v3"] = {x=verts[v[3]].x,y=verts[v[3]].y,z=verts[v[3]].z}
329+
--print("triangle ", i, "v1: ", triangle["v1"].x, triangle["v1"].y, triangle["v1"].z)
330+
--print("triangle ", i, "v2: ", triangle["v2"].x, triangle["v2"].y, triangle["v2"].z)
331+
--print("triangle ", i, "v3: ", triangle["v3"].x, triangle["v3"].y, triangle["v3"].z)
332+
-- round down to get the min vertex of the triangle
333+
local facemin = {x=math.max(0, rd(math.min(triangle["v1"].x, triangle["v2"].x, triangle["v3"].x))),
334+
y=math.max(0, rd(math.min(triangle["v1"].y, triangle["v2"].y, triangle["v3"].y))),
335+
z=math.max(0, rd(math.min(triangle["v1"].z, triangle["v2"].z, triangle["v3"].z)))}
336+
-- round up to get the max vertex of the triangle and make sure it is at least 1
337+
local facemax = {x=math.max(facemin.x+1, ru(math.max(triangle["v1"].x, triangle["v2"].x, triangle["v3"].x))),
338+
y=math.max(facemin.y+1, ru(math.max(triangle["v1"].y, triangle["v2"].y, triangle["v3"].y))),
339+
z=math.max(facemin.z+1, ru(math.max(triangle["v1"].z, triangle["v2"].z, triangle["v3"].z)))}
340+
--print("facemin x: ", facemin.x, "y: ", facemin.y, "z: ", facemin.z)
341+
--print("facemax x: ", facemax.x, "y: ", facemax.y, "z: ", facemax.z)
342+
-- print("verts ", v[1], v[2], v[3])
343+
-- print("texture verts ", t[1], t[2], t[3])
344+
local uvTriangle = {}
345+
uvTriangle["v1"] = {x=vertexTextures[t[1]].x, y=vertexTextures[t[1]].y}
346+
uvTriangle["v2"] = {x=vertexTextures[t[2]].x, y=vertexTextures[t[2]].y}
347+
uvTriangle["v3"] = {x=vertexTextures[t[3]].x, y=vertexTextures[t[3]].y}
348+
-- print("vt[1]=", vertexTextures[t[1]].x, vertexTextures[t[1]].y)
349+
-- print("vt[2]=", vertexTextures[t[2]].x, vertexTextures[t[2]].y)
350+
-- print("vt[3]=", vertexTextures[t[3]].x, vertexTextures[t[3]].y)
351+
352+
for x = facemin.x, facemax.x do
353+
for y = facemin.y, facemax.y do
354+
for z = facemin.z, facemax.z do
355+
AABB_min = {x = x, y = y, z = z}
356+
local result = test_AABB_triangle_intersection(triangle, AABB_min)
357+
if result then
358+
-- make a unique key for this vert
359+
local key = makeVertexKey(x,y,z)
360+
-- if we haven't seen this vert before, add it to our output set
361+
-- keep track of how many voxel positions we have just for debugging purposes
362+
if not uniqueVoxels[key] then
363+
--print("unique voxel x: ", x, "y: ", y, "z: ", z)
364+
uniqueVoxels[key] = {x=x, y=y, z=z}
365+
uniqueCount = uniqueCount + 1
366+
local voxel_center = {x = x + 0.5, y = y + 0.5, z = z + 0.5}
367+
-- print("voxel center x: ", voxel_center.x, "y: ", voxel_center.y, "z: ", voxel_center.z)
368+
local closest_point = closest_point_in_triangle_3d(triangle, voxel_center)
369+
local uv = getUVFromPoint(closest_point, triangle, uvTriangle)
370+
-- print("UV ---->", uv.x, uv.y)
371+
uniqueVoxelColors[key] = getTextureColor(uv.x, 1-uv.y, r_channel, g_channel, b_channel)
372+
-- print(uniqueVoxelColors[key].r .. " " .. uniqueVoxelColors[key].g .. " " .. uniqueVoxelColors[key].b)
373+
else
374+
-- if we have seen this vert before, increment the count
375+
-- this is useful for debugging purposes
376+
repeatVoxelsCount = repeatVoxelsCount + 1
377+
--print("repeat voxel x: ", x, "y: ", y, "z: ", z)
378+
end
379+
end
380+
end
381+
end
382+
end
383+
end
384+
return uniqueVoxels, uniqueVoxelColors, uniqueCount
385+
end

0 commit comments

Comments
 (0)