|
| 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