-
-
Save Jaybob/a3d7e3730d4f75feaaa0 to your computer and use it in GitHub Desktop.
An editor to create 3d pixel meshes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--# 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