What's new
Heapleak - Scripthub

Get the most out of HeapLeak by creating a free account! Once signed in, you’ll gain full access to restricted content, be able to share your own scripts, and participate in our member-only discussions.

Jujutsu Shenanigans [OBJ To Build]

Version / Update: v1.0.0
Download / Script Link
local plr = game:GetService("Players").LocalPlayer;
local rs = game:GetService("ReplicatedStorage");
local buildevt = rs.Remotes.BuildEvent;

local objfile = "model.obj";
local mtlfile = "model.mtl";
local voxsize = 0.5;
local offset = plr.Character.HumanoidRootPart.Position;
local centermodel = true;

local createdparts = {};
local waitingforparts = false;

buildevt.OnClientEvent:Connect(function(parts)
if parts and waitingforparts then
for _, part in parts do
table.insert(createdparts, part);
end
end
end)

local function loadtexture(filename)
local filepath = filename .. ".txt";
if not isfile(filepath) then
warn("texture file not found:", filepath);
return nil;
end

local data = readfile(filepath);
local lines = {};
for line in data:gmatch("[^\r\n]+") do
table.insert(lines, line);
end

local width, height = lines[1]:match("(%d+)%s+(%d+)");
width = tonumber(width);
height = tonumber(height);

local pixels = {};
for i = 2, #lines do
local r, g, b = lines[i]:match("([%d%.]+)%s+([%d%.]+)%s+([%d%.]+)");
if r and g and b then
table.insert(pixels, Color3.new(tonumber(r), tonumber(g), tonumber(b)));
end
end

print("loaded texture:", filename, width .. "x" .. height, #pixels, "pixels");

return {
width = width,
height = height,
pixels = pixels
};
end

local function sampletexture(tex, u, v)
if not tex then return Color3.new(0.8, 0.8, 0.8); end

u = u % 1;
v = v % 1;
if u < 0 then u = u + 1; end
if v < 0 then v = v + 1; end

v = 1 - v;

local x = math.floor(u * tex.width);
local y = math.floor(v * tex.height);

x = math.clamp(x, 0, tex.width - 1);
y = math.clamp(y, 0, tex.height - 1);

local idx = y * tex.width + x + 1;

return tex.pixels[idx] or Color3.new(0.8, 0.8, 0.8);
end

local function parsemtl(data)
local mats = {};
local current;

for line in data:gmatch("[^\r\n]+") do
local stripped = line:match("^%s*(.-)%s*$");
if stripped:sub(1, 1) ~= "#" and stripped ~= "" then
local cmd = stripped:match("^(%S+)");

if cmd == "newmtl" then
current = stripped:match("newmtl%s+(.+)");
mats[current] = {color = Color3.new(0.8, 0.8, 0.8), texture = nil};
elseif cmd == "Kd" and current then
local r, g, b = stripped:match("Kd%s+([%d%.%-eE]+)%s+([%d%.%-eE]+)%s+([%d%.%-eE]+)");
if r and g and b then
mats[current].color = Color3.new(tonumber(r), tonumber(g), tonumber(b));
end
elseif cmd == "map_Kd" and current then
local texfile = stripped:match("map_Kd%s+(.+)");
if texfile then
texfile = texfile:match("([^/\\]+)$");
print("loading texture:", texfile, "for material:", current);
mats[current].texture = loadtexture(texfile);
end
end
end
end

return mats;
end

local function parseobj(data)
local verts = {};
local uvs = {};
local faces = {};
local currentmtl;

for line in data:gmatch("[^\r\n]+") do
local stripped = line:match("^%s*(.-)%s*$");
if stripped:sub(1, 1) ~= "#" and stripped ~= "" then
local cmd = stripped:match("^(%S+)");

if cmd == "v" then
local x, y, z = stripped:match("v%s+([%d%.%-eE]+)%s+([%d%.%-eE]+)%s+([%d%.%-eE]+)");
if x and y and z then
table.insert(verts, Vector3.new(tonumber(x), tonumber(y), tonumber(z)));
end
elseif cmd == "vt" then
local u, v = stripped:match("vt%s+([%d%.%-eE]+)%s+([%d%.%-eE]+)");
if u and v then
table.insert(uvs, Vector2.new(tonumber(u), tonumber(v)));
end
elseif cmd == "f" then
local vindices = {};
local uvindices = {};

for vert in stripped:gmatch("(%S+)") do
if vert ~= "f" then
local parts = {};
for part in vert:gmatch("[^/]+") do
table.insert(parts, part);
end

if #parts >= 1 then
local vidx = tonumber(parts[1]);
if vidx < 0 then vidx = #verts + vidx + 1; end
table.insert(vindices, vidx);
end

if #parts >= 2 and parts[2] ~= "" then
local uvidx = tonumber(parts[2]);
if uvidx < 0 then uvidx = #uvs + uvidx + 1; end
table.insert(uvindices, uvidx);
end
end
end

if #vindices >= 3 then
table.insert(faces, {
vindices = vindices,
uvindices = #uvindices > 0 and uvindices or nil,
mtl = currentmtl
});
end
elseif cmd == "usemtl" then
currentmtl = stripped:match("usemtl%s+(.+)");
end
end
end

print("parsed", #verts, "verts,", #uvs, "uvs,", #faces, "faces");
return verts, uvs, faces;
end

local function getbounds(verts)
local minv = Vector3.new(math.huge, math.huge, math.huge);
local maxv = Vector3.new(-math.huge, -math.huge, -math.huge);

for _, v in verts do
minv = Vector3.new(math.min(minv.X, v.X), math.min(minv.Y, v.Y), math.min(minv.Z, v.Z));
maxv = Vector3.new(math.max(maxv.X, v.X), math.max(maxv.Y, v.Y), math.max(maxv.Z, v.Z));
end

return minv, maxv;
end

local function barycentric(p, a, b, c)
local v0 = b - a;
local v1 = c - a;
local v2 = p - a;

local d00 = v0:Dot(v0);
local d01 = v0:Dot(v1);
local d11 = v1:Dot(v1);
local d20 = v2:Dot(v0);
local d21 = v2:Dot(v1);

local denom = d00 * d11 - d01 * d01;
if math.abs(denom) < 0.0001 then
return 0.33, 0.33, 0.33;
end

local v = (d11 * d20 - d01 * d21) / denom;
local w = (d00 * d21 - d01 * d20) / denom;
local u = 1 - v - w;

return u, v, w;
end

local function triangleboxoverlap(boxcenter, boxhalfsize, v0, v1, v2)
local v0c = v0 - boxcenter;
local v1c = v1 - boxcenter;
local v2c = v2 - boxcenter;

local e0 = v1c - v0c;
local e1 = v2c - v1c;
local e2 = v0c - v2c;

local fex = math.abs(e0.X);
local fey = math.abs(e0.Y);
local fez = math.abs(e0.Z);

local p0 = e0.Z * v0c.Y - e0.Y * v0c.Z;
local p2 = e0.Z * v2c.Y - e0.Y * v2c.Z;
local rad = fez * boxhalfsize.Y + fey * boxhalfsize.Z;
if math.min(p0, p2) > rad or math.max(p0, p2) < -rad then return false; end

p0 = -e0.Z * v0c.X + e0.X * v0c.Z;
p2 = -e0.Z * v2c.X + e0.X * v2c.Z;
rad = fez * boxhalfsize.X + fex * boxhalfsize.Z;
if math.min(p0, p2) > rad or math.max(p0, p2) < -rad then return false; end

p0 = e0.Y * v0c.X - e0.X * v0c.Y;
local p1 = e0.Y * v1c.X - e0.X * v1c.Y;
rad = fey * boxhalfsize.X + fex * boxhalfsize.Y;
if math.min(p0, p1) > rad or math.max(p0, p1) < -rad then return false; end

fex = math.abs(e1.X);
fey = math.abs(e1.Y);
fez = math.abs(e1.Z);

p0 = e1.Z * v0c.Y - e1.Y * v0c.Z;
p2 = e1.Z * v2c.Y - e1.Y * v2c.Z;
rad = fez * boxhalfsize.Y + fey * boxhalfsize.Z;
if math.min(p0, p2) > rad or math.max(p0, p2) < -rad then return false; end

p0 = -e1.Z * v0c.X + e1.X * v0c.Z;
p2 = -e1.Z * v2c.X + e1.X * v2c.Z;
rad = fez * boxhalfsize.X + fex * boxhalfsize.Z;
if math.min(p0, p2) > rad or math.max(p0, p2) < -rad then return false; end

p0 = e1.Y * v0c.X - e1.X * v0c.Y;
p1 = e1.Y * v1c.X - e1.X * v1c.Y;
rad = fey * boxhalfsize.X + fex * boxhalfsize.Y;
if math.min(p0, p1) > rad or math.max(p0, p1) < -rad then return false; end

fex = math.abs(e2.X);
fey = math.abs(e2.Y);
fez = math.abs(e2.Z);

p0 = e2.Z * v0c.Y - e2.Y * v0c.Z;
p1 = e2.Z * v1c.Y - e2.Y * v1c.Y;
rad = fez * boxhalfsize.Y + fey * boxhalfsize.Z;
if math.min(p0, p1) > rad or math.max(p0, p1) < -rad then return false; end

p0 = -e2.Z * v0c.X + e2.X * v0c.Z;
p1 = -e2.Z * v1c.X + e2.X * v1c.Z;
rad = fez * boxhalfsize.X + fex * boxhalfsize.Z;
if math.min(p0, p1) > rad or math.max(p0, p1) < -rad then return false; end

p0 = e2.Y * v0c.X - e2.X * v0c.Y;
p2 = e2.Y * v2c.X - e2.X * v2c.Y;
rad = fey * boxhalfsize.X + fex * boxhalfsize.Y;
if math.min(p0, p2) > rad or math.max(p0, p2) < -rad then return false; end

local minc = Vector3.new(math.min(v0c.X, v1c.X, v2c.X), math.min(v0c.Y, v1c.Y, v2c.Y), math.min(v0c.Z, v1c.Z, v2c.Z));
local maxc = Vector3.new(math.max(v0c.X, v1c.X, v2c.X), math.max(v0c.Y, v1c.Y, v2c.Y), math.max(v0c.Z, v1c.Z, v2c.Z));

if minc.X > boxhalfsize.X or maxc.X < -boxhalfsize.X then return false; end
if minc.Y > boxhalfsize.Y or maxc.Y < -boxhalfsize.Y then return false; end
if minc.Z > boxhalfsize.Z or maxc.Z < -boxhalfsize.Z then return false; end

return true;
end

local function voxelize(verts, uvs, faces, mats, size)
local voxmap = {};
local minbound, maxbound = getbounds(verts);

if centermodel then
local center = (minbound + maxbound) / 2;
for i, v in verts do
verts[i] = v - center;
end
minbound, maxbound = getbounds(verts);
end

local boxhalfsize = Vector3.new(size / 2, size / 2, size / 2);

for fidx, face in faces do
local vindices = face.vindices;
if #vindices >= 3 then
local v0 = verts[vindices[1]];
local v1 = verts[vindices[2]];
local v2 = verts[vindices[3]];

if not v0 or not v1 or not v2 then
continue;
end

local mat = face.mtl and mats[face.mtl];
local tex = mat and mat.texture;

local uv0, uv1, uv2;
if face.uvindices and #face.uvindices >= 3 then
uv0 = uvs[face.uvindices[1]];
uv1 = uvs[face.uvindices[2]];
uv2 = uvs[face.uvindices[3]];
end

local trimin = Vector3.new(
math.min(v0.X, v1.X, v2.X),
math.min(v0.Y, v1.Y, v2.Y),
math.min(v0.Z, v1.Z, v2.Z)
);
local trimax = Vector3.new(
math.max(v0.X, v1.X, v2.X),
math.max(v0.Y, v1.Y, v2.Y),
math.max(v0.Z, v1.Z, v2.Z)
);

local startx = math.floor(trimin.X / size) * size;
local starty = math.floor(trimin.Y / size) * size;
local startz = math.floor(trimin.Z / size) * size;
local endx = math.ceil(trimax.X / size) * size;
local endy = math.ceil(trimax.Y / size) * size;
local endz = math.ceil(trimax.Z / size) * size;

local x = startx;
while x <= endx do
local y = starty;
while y <= endy do
local z = startz;
while z <= endz do
local boxcenter = Vector3.new(x + size / 2, y + size / 2, z + size / 2);

if triangleboxoverlap(boxcenter, boxhalfsize, v0, v1, v2) then
local color = mat and mat.color or Color3.new(0.8, 0.8, 0.8);

if tex and uv0 and uv1 and uv2 then
local u, v, w = barycentric(boxcenter, v0, v1, v2);
local uvcoord = uv0 * u + uv1 * v + uv2 * w;
color = sampletexture(tex, uvcoord.X, uvcoord.Y);
end

local key = string.format("%.3f,%.3f,%.3f", x, y, z);
voxmap[key] = {pos = Vector3.new(x, y, z), color = color};
end

z = z + size;
end
y = y + size;
end
x = x + size;
end
end

if fidx % 100 == 0 then
print("voxelizing face", fidx, "/", #faces);
task.wait();
end
end

return voxmap;
end

local function build()
if not isfile(objfile) then
warn("obj file not found");
return;
end

print("parsing obj file...");
local objdata = readfile(objfile);
local verts, uvs, faces = parseobj(objdata);

local mats = {};
if isfile(mtlfile) then
print("parsing mtl file...");
local mtldata = readfile(mtlfile);
mats = parsemtl(mtldata);
end

print("voxelizing mesh...");
local voxmap = voxelize(verts, uvs, faces, mats, voxsize);

local voxlist = {};
for _, vox in voxmap do
table.insert(voxlist, vox);
end

print("placing", #voxlist, "voxels");

waitingforparts = true;

for i, vox in voxlist do
local worldpos = offset + vox.pos;
createdparts = {};

buildevt:FireServer("Create", "Part", worldpos);

local timeout = 0;
while #createdparts == 0 and timeout < 50 do
task.wait(0.01);
timeout += 1;
end

if #createdparts > 0 then
buildevt:FireServer("Color", {createdparts[1]}, vox.color);
end

if i % 50 == 0 then
print("progress:", i, "/", #voxlist);
task.wait(0.1);
end
end

waitingforparts = false;
print("build complete!");
end

build();
[ View More ]
00a5700b-ce29-429c-94f8-4e69b696d51f.webp


take any 3d model and put it in ur exploits workspace and rename it to "model.obj" aswell as the mtl. From my discord server of unfinished projects. I won't update this.
 
Back
Top