Skip to content

Instantly share code, notes, and snippets.

@Jaybob

Jaybob/3dPixel Secret

Created May 11, 2015 00:52
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Jaybob/a3d7e3730d4f75feaaa0 to your computer and use it in GitHub Desktop.
Save Jaybob/a3d7e3730d4f75feaaa0 to your computer and use it in GitHub Desktop.
An editor to create 3d pixel meshes
--# Main
displayMode(OVERLAY)
function setup()
imageSetup()
BLOCKWIDTH = 20
TEXTURE = defaultTexture -- readImage("Documents:testTex")
TEXRANGE = {
{vec2(0,0),vec2(1,1)}
}
TEXINDEX = 1
COLORS = {
color(255, 255, 255, 255),
color(42, 190, 217, 255),
color(193, 80, 80, 255),
color(237, 160, 41, 255),
color(98, 45, 173, 255),
color(69, 96, 208, 255),
color(179, 204, 44, 255),
color(52, 132, 124, 255),
color(146, 194, 77, 255)}
parameter.text("SaveAs")
parameter.action("SaveMesh",function()save()end)
touches = {}
cam = Camera()
ls,rs = Stick(10),Stick(3,WIDTH-120)
col = COLORS[1]
model = Model()
ui = Ui()
mode = "Add"
end
function draw()
background(40, 40, 50)
perspective()
cam:draw()
drawScene()
model:draw()
ortho()
viewMatrix(matrix())
ui:draw()
ls:draw()
rs:draw()
end
function touched(touch)
if touch.state == ENDED then
touches[touch.id] = nil
else
touches[touch.id] = touch
end
if touch.y <= HEIGHT-40 then
model:touched(touch)
else
ui:touched(touch)
end
end
function drawScene()
strokeWidth(2)
pushMatrix()
translate(0,-200,0) rotate(90,1,0,0)
for i = -5,5 do
line(-500,i*100,500,i*100)line(i*100,-500,i*100,500)
end
popMatrix()
end
function imageSetup()
pushStyle()
strokeWidth(4)
fill(0, 0, 0, 255)
stroke(255, 255, 255, 255)
ghostImg = image(50,50)
setContext(ghostImg) rect(-1,-1,52,52) setContext()
fill(255, 255, 255, 255)
stroke(0, 0, 0, 255)
defaultTexture = image(50,50)
setContext(defaultTexture)
rect(-1,-1,52,52)
setContext()
popStyle()
end
function rRect(w,h,r)
strokeWidth(0)
local img = image(w,h)
fill(255, 255, 255, 255)
setContext(img)
pushMatrix()
ellipse(r/2,h-r/2,r)ellipse(w-r/2,h-r/2,r)
ellipse(r/2,r/2,r)ellipse(w-r/2,r/2,r)
rect(0,r/2,w,h-r) rect(r/2,0,w-r,h)
popMatrix()
setContext()
return img
end
function intersectPlane(planePos,planeNorm,a,b)
local b = b or vec3(0,0,0)
local ad = (a-planePos):dot(planeNorm)
local bd = (b-planePos):dot(planeNorm)
if ad > 0 and bd < 0 or ad < 0 and bd > 0 then
local intersection = a+((a-b):normalize()*(a:dist(b)/(a-b):dot(-planeNorm)*ad))
return true,intersection
end
return false
end
function save()
if SaveAs ~= "" then
local s = saveMesh()
saveGlobalData(SaveAs,s)
output.clear()
print('Done, saved to global data using key "'..SaveAs..'"')
print("Load mesh into a project using...")
print(
'data = loadstring(readGlobalData("'..SaveAs..'"))()\n'..
"m = mesh()\n"..
"m.vertices = data.vertices\n"..
"m.colors = data.colors\n"..
'm.texture = "someTexture"\n'..
"m.texCoords = data.texCoords"
)
else
print("No name given to save as")
end
end
function saveMesh()
print("Copying Data")
local tempVert,tempCol,tempTex = {},{},{}
for i = 1,#model.meshVerts do
table.insert(tempVert,model.meshVerts[i])
table.insert(tempCol,model.meshColors[i])
table.insert(tempTex,model.meshTexCoords[i])
end
print("Checking Doubles")
local r = {}
for i = 1,#tempVert-3,3 do
local a,b,c = tempVert[i],tempVert[i+1],tempVert[i+2]
for j = i+3,#tempVert,3 do
if a == tempVert[j] and b == tempVert[j+1] and c == tempVert[j+2] then
table.insert(r,i)
table.insert(r,j)
end
end
end
print("Sorting")
table.sort(r,function(a,b)
return a > b
end)
print("Deleting Doubles")
for k,v in ipairs(r) do
for i = 1,3 do
table.remove(tempVert,v)
table.remove(tempCol,v)
table.remove(tempTex,v)
end
end
print("Saving")
local s = "return {"
s = s.."vertices = {"
for l,n in ipairs(tempVert) do
s = s.."vec3("..n.x..","..n.y..","..n.z.."),"
end
s = s.."},"
print("Verts Saved")
s = s.."colors = {"
for l,n in ipairs(tempCol) do
s = s.."color("..n.r..","..n.g..","..n.b..","..n.a.."),"
end
s = s.."},"
print("Colors Saved")
s = s.."texCoords = {"
for l,n in ipairs(tempTex) do
s = s.."vec2("..n.x..","..n.y.."),"
end
s = s.."}"
print("TexCoords Saved")
s = s.."}"
return s
end
--# Model
Model = class()
function Model:init()
self.vectors = {vec3(-1,0,0),vec3(1,0,0),vec3(0,-1,0),vec3(0,1,0),vec3(0,0,-1),vec3(0,0,1)}
self.revVec = {2,1,4,3,6,5}
self.array = {}
self.bit = {}
for i = -50,50 do
self.bit[i] = {}
for j = -50,50 do
self.bit[i][j] = {}
end
end
w = BLOCKWIDTH/2
W = BLOCKWIDTH
self.boxVerts = {
vec3(-w,-w, w),vec3(-w, w, w),vec3( w, w, w),vec3( w,-w, w),
vec3(-w,-w,-w),vec3(-w, w,-w),vec3( w, w,-w),vec3( w,-w,-w)}
self.boxFaces = {1,2,3,1,4,3,5,6,7,5,8,7,5,6,2,5,1,2,8,7,3,8,4,3,2,6,7,2,3,7,1,5,8,1,4,8}
local t = TEXRANGE[TEXINDEX]
self.texPoints = {vec2(0,0),vec2(0,1),vec2(1,1),vec2(1,0)}
self.boxTex = {1,2,3,1,4,3,1,2,3,1,4,3,1,2,3,1,4,3,1,2,3,1,4,3,1,2,3,1,4,3,1,2,3,1,4,3}
self.sel = mesh()
self.sel.texture = ghostImg
local tc = {}
local temp = {}
for k,v in ipairs(self.boxFaces) do
temp[#temp+1] = self.boxVerts[v]
tc[#tc+1] = self.texPoints[self.boxTex[k]]
end
self.sel.vertices = temp
self.sel.texCoords = tc
self.texPoints = {t[1],vec2(t[1].x,t[2].y),t[2],vec2(t[2].x,t[1].y)}
self.ghost = {}
self.meshVerts = {}
self.meshColors = {}
self.meshTexCoords = {}
self.mesh = mesh()
self.mesh.texture = TEXTURE
self:addCube(vec3(0,0,0))
self:updateMesh()
self.moving = false
end
function Model:addCube(pos)
table.insert(self.array,pos)
for i = 1,36 do
table.insert(self.meshVerts,self.boxVerts[self.boxFaces[i]]+(pos*W))
table.insert(self.meshColors,col)
table.insert(self.meshTexCoords,self.texPoints[self.boxTex[i]])
end
self.bit[pos.x][pos.y][pos.z] = {1,1,1,1,1,1}
for k,v in ipairs(self.vectors) do
local t = pos + v
local target = self.bit[t.x][t.y][t.z]
if target then
self.bit[pos.x][pos.y][pos.z][k] = 0
self.bit[t.x][t.y][t.z][self.revVec[k]] = 0
end
end
end
function Model:deleteCube(pos,idx)
for k,v in ipairs(self.vectors) do
local t = pos + v
local target = self.bit[t.x][t.y][t.z]
if target then
self.bit[t.x][t.y][t.z][self.revVec[k]] = 1
end
end
self.bit[pos.x][pos.y][pos.z] = nil
local idxL = (idx * 36) - 35
for i = 1,36 do
table.remove(self.meshVerts,idxL)
table.remove(self.meshColors,idxL)
table.remove(self.meshTexCoords,idxL)
end
table.remove(self.array,idx)
end
function Model:updateMesh()
self.mesh.vertices = self.meshVerts
self.mesh.colors = self.meshColors
self.mesh.texCoords = self.meshTexCoords
end
function Model:draw()
local t = TEXRANGE[TEXINDEX]
self.texPoints = {t[1],vec2(t[1].x,t[2].y),t[2],vec2(t[2].x,t[1].y)}
fill(0, 129, 255, 255)
if self.ghost and self.moving then
for k,v in ipairs(self.ghost) do
pushMatrix()
local d = v*W
translate(d:unpack())
self.sel:draw()
popMatrix()
end
end
fill(7, 255, 0, 255)
if self.anchor and self.moving then
local d = self.array[self.anchor.key]*W
pushMatrix()
translate(d:unpack())
scale(1.1)
self.sel:draw()
popMatrix()
end
self.mesh:draw()
if touches[self.tid] == nil then
self.tid = nil
end
end
function Model:reTexture(k)
local k = (k * 36) - 36
for i = 1,36 do
self.meshTexCoords[i+k] = self.texPoints[self.boxTex[i]]
end
self:updateMesh()
end
function Model:reColor(k)
local k = (k * 36) - 36
for i = 1,36 do
self.meshColors[i+k] = col
end
self:updateMesh()
end
function Model:touched(touch)
if touch.state == BEGAN then
self.anchor = self:getTouched(touch)
if self.anchor then
self.tid = touch.id
self.ghost = {self.anchor.tar}
end
elseif touch.state == MOVING then
if self.anchor then
if mode == "Add" then
self.moving = true
self.ghost = {self.anchor.tar}
local t = cam:zTouch(touch)+cam.eye
local hit,int = intersectPlane(self.anchor.tar*W,self.anchor.vec,cam.eye,t)
if hit then
local l = {dist = 0,v = vec3(0,0,0)}
for i = 1,6 do
local v = self.vectors[i]
if self.anchor.vec ~= -v and self.anchor.vec ~= v then
local d = (int-(self.anchor.tar*W)):dot(v)
d = math.min(20,math.floor(d/W))
if d > l.dist then
l = {dist = d,v = v}
end
end
end
if l.dist ~= 0 then
local a,b = self.anchor.tar,self.anchor.tar+(l.v*l.dist)
local p = a+l.v
while p ~= b do
if self.bit[p.x][p.y][p.z] then
goto next
end
table.insert(self.ghost,p)
p = p + l.v
end
if not self.bit[b.x][b.y][b.z] then
table.insert(self.ghost,b)
end
::next::
end
end
end
end
elseif touch.state == ENDED then
self.moving = false
if mode == "Add" then
if #self.ghost > 0 then
for k,v in ipairs(self.ghost) do
self:addCube(v)
end
self:updateMesh()
end
elseif mode == "Delete" then
if self.anchor then
self:deleteCube(self.array[self.anchor.key],self.anchor.key)
self:updateMesh()
end
elseif mode == "Re-Tex" then
if self.anchor then
self:reTexture(self.anchor.key)
end
elseif mode == "Re-Col" then
if self.anchor then
self:reColor(self.anchor.key)
end
end
self.ghost = {}
self.anchor = nil
self.endPoint = nil
end
end
function Model:getTouched(touch)
local low = {dist = 100000}
local t = cam:zTouch(touch)+cam.eye
for k,v in ipairs(self.array) do
local bit = self.bit[v.x][v.y][v.z]
if bit then
local cen = v * W
for i = 1,6 do
if bit[i] == 1 then
local vec = self.vectors[i]
if (((v*W)+(vec*w))-cam.eye):dot(vec) < 0 then
local pos = vec*w+cen
local hit,int = intersectPlane(pos,vec,cam.eye,t)
if hit and int:dist(pos) < w then
local d = int:dist(cam.eye)
if d < low.dist then
low = {key=k,dist=d,tar=v+vec,vec=vec}
end
end
end
end
end
end
end
if low.dist ~= 100000 then
return low
end
end
--# Camera
Camera = class()
function Camera:init(eX,eY,eZ,lX,lY,lZ)
self.eye = vec3(eX or 0, eY or 0, eZ or 400)
self.lat = vec3(lX or 0, lY or 0, lZ or 0)
self.dist = self.lat:dist(self.eye)
self.angH = 90
self.angV = -90
self.fix = true
self.crosshair = true
end
function Camera:draw()
self.angH = self.angH + rs.x
self.angV = self.angV + rs.y
self:moveLocal(ls.x,nil,ls.y)
self.Z = (self.lat-self.eye):normalize()
self.X = self.Z:cross(vec3(0,1,0)):normalize()
self.Y = self.X:cross(self.Z):normalize()
self.angV = math.min(-1,math.max(-179,self.angV))
if self.fix then
self.eye = -self:rotatePoint() + self.lat
else
self.lat = self:rotatePoint() + self.eye
end
camera(self.eye.x, self.eye.y, self.eye.z,self.lat.x, self.lat.y, self.lat.z)
self:drawCrosshair()
self. mat = modelMatrix()*viewMatrix()*projectionMatrix()
end
function Camera:moveLocal(x,y,z)
if x and x ~= 0 then
local xVel = self.X * x
self.eye = self.eye + xVel
self.lat = self.lat + xVel
end
if y and y ~= 0 then
local yVel = self.Y * y
self.eye = self.eye + yVel
self.lat = self.lat + yVel
end
if z and z ~= 0 then
local zVel = self.Z * z
self.eye = self.eye + zVel
self.lat = self.lat + zVel
end
end
function Camera:rotatePoint()
-- calculate y and z from angV at set distance
local y = math.cos(math.rad(self.angV))*self.dist
local O = math.sin(math.rad(self.angV))*self.dist
-- calculate x and z from angH using O as the set distance
local x = math.cos(math.rad(self.angH))*O
local z = math.sin(math.rad(self.angH))*O
return vec3(x,y,z)
end
function Camera:drawCrosshair(w)
local w = w or 50
pushMatrix()pushStyle()
translate(self.lat.x,self.lat.y, self.lat.z)
strokeWidth(2)
stroke(38, 255, 0, 255)
line(-w/2,0,w/2,0)
stroke(255, 187, 0, 255)
line(0,-w/2,0,w/2) rotate(90,1,0,0)
stroke(73, 0, 255, 255)
line(0,-w/2,0,w/2)
popMatrix()popStyle()
end
function Camera:screenPos(p)
local m = self.mat
m = m:translate(p.x, p.y, p.z)
local X, Y = (m[13]/m[16]+1)*WIDTH/2, (m[14]/m[16]+1)*HEIGHT/2
return vec2(X,Y)
end
function Camera:zTouch(touch,depth)
local depth = depth or 2000
local zDepth = 0.9086 -- adjust for different fov
local x = depth / zDepth * cam.X * ( touch.x / WIDTH - 0.5)
local y = depth / zDepth * cam.Y * ((touch.y / HEIGHT - 0.5) / (WIDTH/HEIGHT))
return x + y + (depth * cam.Z)
end
Stick = class()
function Stick:init(ratio,x,y)
self.ratio = ratio or 1
self.i = vec2(x or 120,y or 120)
self.v = vec2(0,0)
self.b = b or 180
self.s = s or 100
self.d = d or 50
self.a = 0
self.touchId = nil
self.x,self.y = 0,0
end
function Stick:draw()
if touches[self.touchId] == nil then
for i,t in pairs(touches) do
if vec2(t.x,t.y):dist(self.i) < self.b/2 then self.touchId = i end
end
self.v = vec2(0,0)
else
self.v = vec2(touches[self.touchId].x,touches[self.touchId].y) - self.i
self.a = math.deg(math.atan2(self.v.y,self.v.x))
end
self.t = math.min(self.b/2,self.v:len())
if self.t >= self.b/2 then
self.v = vec2(math.cos(math.rad(self.a))*self.b/2,math.sin(math.rad(self.a))*self.b/2)
end
fill(127, 127, 127, 150)
ellipse(self.i.x,self.i.y,self.b)
ellipse(self.i.x+self.v.x,self.i.y+self.v.y,self.s)
self.v = self.v/(self.b/2)*self.ratio
self.t = self.t/(self.b/2)*self.ratio
self.x,self.y = self.v.x,self.v.y
end
--# Ui
Ui = class()
function Ui:init()
self.smallBtn = rRect(30,30,10)
self.largeBtn = rRect(100,30,10)
self.tools = {"Add","Delete","Re-Col","Re-Tex"}
self.texImg = {}
local texW,texH = TEXTURE.width,TEXTURE.height
for k,v in ipairs(TEXRANGE) do
local w,h = texW*(v[2].x-v[1].x),texH*(v[2].y-v[1].y)
local img = image(w,h)
setContext(img)
sprite(TEXTURE,(texW/2)-(texW*v[1].x),(texH/2)-(texH*v[1].y))
setContext()
table.insert(self.texImg,img)
end
end
function Ui:draw()
noStroke()
fill(59, 59, 59, 255)
rect(-1,HEIGHT-40,WIDTH+1,41)
pushMatrix()
for k,v in ipairs(self.tools) do
if mode == v then
tint(47, 47, 47, 255)
else
tint(96, 96, 96, 255)
end
translate(105,0)
sprite(self.largeBtn,0,HEIGHT-20)
fill(214, 214, 214, 255)
text(v,0,HEIGHT-20)
end
popMatrix()
tint(127, 127, 127, 255)
sprite(self.largeBtn,590,HEIGHT-20)
tint(255, 255, 255, 255)
sprite(self.texImg[TEXINDEX],590,HEIGHT-20,28,28)
for k,v in ipairs(COLORS) do
fill(v)
local s
if v == col then
s = 30
else
s = 20
end
ellipse(k*35+685,HEIGHT-20,s)
end
end
function Ui:touched(touch)
if touch.state == ENDED then
if touch.y > HEIGHT-40 then
for i = 1,4 do
local x = i * 110 - 50
if touch.x > x and touch.x < x + 100 then
mode = self.tools[i]
end
end
if touch.x > 540 and touch.x < 540 + 100 then
if TEXINDEX == #TEXRANGE then
TEXINDEX = 1
else
TEXINDEX = TEXINDEX + 1
end
end
for k,v in ipairs(COLORS) do
local x = k * 35 + 685 - 15
if touch.x > x and touch.x < x + 30 then
col = v
end
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment