Viewer = class() function Viewer:init() self.scale = 10 self.eyecoeff = 2 self.orthogonal = true self.thetax = -8 * math.pi / 24 self.thetaz = -21 * math.pi / 24 self.valid = false self:recalc() end function Viewer:max_scale() if self.valid then local x = math.abs(self.xmin - self.xmax) x = math.min(x, math.abs(self.ymin - self.ymax)) x = math.min(x, math.abs(self.zmin - self.zmax)) return 1000 / x end return 100 end function Viewer:min_scale() if self.valid then local x = math.abs(self.xmin - self.xmax) x = math.min(x, math.abs(self.ymin - self.ymax)) x = math.min(x, math.abs(self.zmin - self.zmax)) return 10 / x end return 0.5 end function Viewer:recalc(invalidate) invalidate = invalidate or false self.valid = true self.xmin = var.recall("xmin") self.xmax = var.recall("xmax") self.ymin = var.recall("ymin") self.ymax = var.recall("ymax") self.zmin = var.recall("zmin") self.zmax = var.recall("zmax") self.axes = var.recall("axes") self.box = var.recall("box") self.xsplit = var.recall("xsplit") self.ysplit = var.recall("ysplit") self.orthogonal = var.recall("orthogonal") if type(self.xmin) ~= "number" then self.valid = false return "xmin" elseif type(self.xmax) ~= "number" then self.valid = false return "xmax" elseif type(self.ymin) ~= "number" then self.valid = false return "ymin" elseif type(self.ymax) ~= "number" then self.valid = false return "ymax" elseif type(self.zmin) ~= "number" then self.valid = false return "zmin" elseif type(self.zmax) ~= "number" then self.valid = false return "zmax" elseif type(self.axes) ~= "boolean" then self.valid = false return "axes" elseif type(self.box) ~= "boolean" then self.valid = false return "box" elseif type(self.xsplit) ~= "number" or self.xsplit < 0 then self.valid = false return "xsplit" elseif type(self.ysplit) ~= "number" or self.ysplit < 0 then self.valid = false return "ysplit" elseif type(self.orthogonal) ~= "boolean" then self.valid = false return "orthogonal" end self.xvalues = {} self.yvalues = {} for i = 0, self.xsplit + 1 do self.xvalues[i] = self.xmin + i * (self.xmax - self.xmin) / (self.xsplit + 1) end for i = 0, self.ysplit + 1 do self.yvalues[i] = self.ymin + i * (self.ymax - self.ymin) / (self.ysplit + 1) end self.fvalues = {} for i = 0, self.xsplit + 1 do self.fvalues[i] = {} for j = 0, self.ysplit + 1 do local z = math.eval("f(" .. self.xvalues[i] .. "," .. self.yvalues[j] ..")") if type(z) == "number" and z >= self.zmin and z <= self.zmax then self.fvalues[i][j] = z else self.fvalues[i][j] = nil end end end self.box_lines = {{{self.xmin, self.ymin, self.zmin},{self.xmin, self.ymin, self.zmax}}, {{self.xmin, self.ymin, self.zmin},{self.xmin, self.ymax, self.zmin}}, {{self.xmin, self.ymin, self.zmin},{self.xmax, self.ymin, self.zmin}}, {{self.xmax, self.ymax, self.zmin},{self.xmax, self.ymax, self.zmax}}, {{self.xmax, self.ymax, self.zmin},{self.xmax, self.ymin, self.zmin}}, {{self.xmax, self.ymax, self.zmin},{self.xmin, self.ymax, self.zmin}}, {{self.xmax, self.ymin, self.zmax},{self.xmax, self.ymin, self.zmin}}, {{self.xmax, self.ymin, self.zmax},{self.xmax, self.ymax, self.zmax}}, {{self.xmax, self.ymin, self.zmax},{self.xmin, self.ymin, self.zmax}}, {{self.xmin, self.ymax, self.zmax},{self.xmin, self.ymax, self.zmin}}, {{self.xmin, self.ymax, self.zmax},{self.xmin, self.ymin, self.zmax}}, {{self.xmin, self.ymax, self.zmax},{self.xmax, self.ymax, self.zmax}}} self.axes_lines = {{{0, 0, 0}, {1.1 * self.xmax, 0, 0}}, {{0, 0, 0}, {0, 1.1 * self.ymax, 0}}, {{0, 0, 0}, {0, 0, 1.1 * self.zmax}}} if invalidate then platform.window:invalidate() end return "" end function Viewer:redraw(gc) if self.valid ~= true then gc:drawString("Error: wrong value for variable "..self:recalc(), 10, 10, "top") return end local fxx = math.cos(self.thetaz) local fxy = math.sin(self.thetaz) local fxz = 0 local fyx = - math.cos(self.thetax) * math.sin(self.thetaz) local fyy = math.cos(self.thetax) * math.cos(self.thetaz) local fyz = math.sin(self.thetax) local fzx = math.sin(self.thetax) * math.sin(self.thetaz) local fzy = - math.sin(self.thetax) * math.cos(self.thetaz) local fzz = math.cos(self.thetax) local eyedistance = self.eyecoeff * math.sqrt(math.max(self.xmin * self.xmin, self.xmax * self.xmax) + math.max(self.ymin * self.ymin, self.ymax * self.ymax) + math.max(self.zmin * self.zmin, self.zmax * self.zmax)) + 1 -- avoid division by 0 local winh = platform.window:height() local winw = platform.window:width() local xoffset = winw / 2 local yoffset = winh / 2 local xoffsetb = (self.xmax + self.xmin) / 2 local yoffsetb = (self.ymax + self.ymin) / 2 local zoffsetb = (self.zmax + self.zmin) / 2 local todraw = {} for i = 0, self.xsplit + 1 do todraw[i] = {} for j = 0, self.ysplit + 1 do if self.fvalues[i][j] ~= nil then -- line along x, y varying if not self.orthogonal then local z = fzx * (self.xvalues[i] - xoffsetb) + fzy * (self.yvalues[j] - yoffsetb) + fzz * (self.fvalues[i][j] - zoffsetb) local factor = self.scale * eyedistance / (eyedistance + z) todraw[i][j] = {(fxx * (self.xvalues[i] - xoffsetb) + fxy * (self.yvalues[j] - yoffsetb) + fxz * (self.fvalues[i][j] - zoffsetb)) * factor + xoffset, (fyx * (self.xvalues[i] - xoffsetb) + fyy * (self.yvalues[j] - yoffsetb) + fyz * (self.fvalues[i][j] - zoffsetb)) * factor + yoffset} else todraw[i][j] = {(fxx * (self.xvalues[i] - xoffsetb) + fxy * (self.yvalues[j] - yoffsetb) + fxz * (self.fvalues[i][j] - zoffsetb)) * self.scale + xoffset, (fyx * (self.xvalues[i] - xoffsetb) + fyy * (self.yvalues[j] - yoffsetb) + fyz * (self.fvalues[i][j] - zoffsetb)) * self.scale + yoffset} end end end end for i = 0, self.ysplit + 1 do todraw[i + self.xsplit + 2] = {} for j = 0, self.xsplit + 1 do if self.fvalues[j][i] ~= nil then -- line along y, x varying if not self.orthogonal then local z = fzx * (self.xvalues[j] - xoffsetb) + fzy * (self.yvalues[i] - yoffsetb) + fzz * (self.fvalues[j][i] - zoffsetb) local factor = self.scale * eyedistance / (eyedistance + z) todraw[i + self.xsplit + 2][j] = {(fxx * (self.xvalues[j] - xoffsetb) + fxy * (self.yvalues[i] - yoffsetb) + fxz * (self.fvalues[j][i] - zoffsetb)) * factor + xoffset, (fyx * (self.xvalues[j] - xoffsetb) + fyy * (self.yvalues[i] - yoffsetb) + fyz * (self.fvalues[j][i] - zoffsetb)) * factor + yoffset} else todraw[i + self.xsplit + 2][j] = {(fxx * (self.xvalues[j] - xoffsetb) + fxy * (self.yvalues[i] - yoffsetb) + fxz * (self.fvalues[j][i] - zoffsetb)) * self.scale + xoffset, (fyx * (self.xvalues[j] - xoffsetb) + fyy * (self.yvalues[i] - yoffsetb) + fyz * (self.fvalues[j][i] - zoffsetb)) * self.scale + yoffset} end end end end gc:setPen("thin", "smooth") for i = 0, self.xsplit + self.ysplit + 3 do for j = 0, math.max(self.ysplit, self.xsplit) do local a = todraw[i][j] local b = todraw[i][j+1] if a and b then gc:drawLine(a[1], a[2], b[1], b[2]) end end end if self.axes then for i = 1, 3 do if not self.orthogonal then local z1 = fzx * (self.axes_lines[i][1][1] - xoffsetb) + fzy * (self.axes_lines[i][1][2] - yoffsetb) + fzz * (self.axes_lines[i][1][3] - zoffsetb) local z2 = fzx * (self.axes_lines[i][2][1] - xoffsetb) + fzy * (self.axes_lines[i][2][2] - yoffsetb) + fzz * (self.axes_lines[i][2][3] - zoffsetb) gc:drawLine((fxx * (self.axes_lines[i][1][1] - xoffsetb) + fxy * (self.axes_lines[i][1][2] - yoffsetb) + fxz * (self.axes_lines[i][1][3] - zoffsetb)) * self.scale * eyedistance / (eyedistance + z1) + xoffset, (fyx * (self.axes_lines[i][1][1] - xoffsetb) + fyy * (self.axes_lines[i][1][2] - yoffsetb) + fyz * (self.axes_lines[i][1][3] - zoffsetb)) * self.scale * eyedistance / (eyedistance + z1) + yoffset, (fxx * (self.axes_lines[i][2][1] - xoffsetb) + fxy * (self.axes_lines[i][2][2] - yoffsetb) + fxz * (self.axes_lines[i][2][3] - zoffsetb)) * self.scale * eyedistance / (eyedistance + z2) + xoffset, (fyx * (self.axes_lines[i][2][1] - xoffsetb) + fyy * (self.axes_lines[i][2][2] - yoffsetb) + fyz * (self.axes_lines[i][2][3] - zoffsetb)) * self.scale * eyedistance / (eyedistance + z2) + yoffset) else gc:drawLine((fxx * (self.axes_lines[i][1][1] - xoffsetb) + fxy * (self.axes_lines[i][1][2] - yoffsetb) + fxz * (self.axes_lines[i][1][3] - zoffsetb)) * self.scale + xoffset, (fyx * (self.axes_lines[i][1][1] - xoffsetb) + fyy * (self.axes_lines[i][1][2] - yoffsetb) + fyz * (self.axes_lines[i][1][3] - zoffsetb)) * self.scale + yoffset, (fxx * (self.axes_lines[i][2][1] - xoffsetb) + fxy * (self.axes_lines[i][2][2] - yoffsetb) + fxz * (self.axes_lines[i][2][3] - zoffsetb)) * self.scale + xoffset, (fyx * (self.axes_lines[i][2][1] - xoffsetb) + fyy * (self.axes_lines[i][2][2] - yoffsetb) + fyz * (self.axes_lines[i][2][3] - zoffsetb)) * self.scale + yoffset) end end local zx = 0 local zy = 0 local zz = 0 if not self.orthogonal then zx = 1.35 * fzx * (self.xmax - xoffsetb) - fzy * yoffsetb - fzz * zoffsetb zy = 1.35 * fzy * (self.ymax - yoffsetb) - fzx * xoffsetb - fzz * zoffsetb zz = 1.35 * fzz * (self.zmax - zoffsetb) - fzx * xoffsetb - fzy * yoffsetb end gc:drawString("x", ( 1.35 * fxx * (self.xmax - xoffsetb) - fxy * yoffsetb - fxz * zoffsetb) * self.scale * eyedistance / (eyedistance + zx) + xoffset, ( 1.35 * fyx * (self.xmax - xoffsetb) - fyy * yoffsetb - fyz * zoffsetb) * self.scale * eyedistance / (eyedistance + zx) + yoffset, "middle") gc:drawString("y", (-fxx * xoffsetb + 1.35 * fxy * (self.ymax - yoffsetb) - fxz * zoffsetb) * self.scale * eyedistance / (eyedistance + zy) + xoffset, (-fyx * xoffsetb + 1.35 * fyy * (self.ymax - yoffsetb) - fyz * zoffsetb) * self.scale * eyedistance / (eyedistance + zy) + yoffset, "middle") gc:drawString("z", (-fxx * xoffsetb - fxy * yoffsetb + 1.35 * fxz * (self.zmax - zoffsetb)) * self.scale * eyedistance / (eyedistance + zx) + xoffset, (-fyx * xoffsetb - fyy * yoffsetb + 1.35 * fyz * (self.zmax - zoffsetb)) * self.scale * eyedistance / (eyedistance + zz) + yoffset, "middle") end if self.box then gc:setPen("thin", "dashed") for i = 1, 12 do if not self.orthogonal then local z1 = fzx * (self.box_lines[i][1][1] - xoffsetb) + fzy * (self.box_lines[i][1][2] - yoffsetb) + fzz * (self.box_lines[i][1][3] - zoffsetb) local z2 = fzx * (self.box_lines[i][2][1] - xoffsetb) + fzy * (self.box_lines[i][2][2] - yoffsetb) + fzz * (self.box_lines[i][2][3] - zoffsetb) gc:drawLine((fxx * (self.box_lines[i][1][1] - xoffsetb) + fxy * (self.box_lines[i][1][2] - yoffsetb) + fxz * (self.box_lines[i][1][3] - zoffsetb)) * self.scale * eyedistance / (eyedistance + z1) + xoffset, (fyx * (self.box_lines[i][1][1] - xoffsetb) + fyy * (self.box_lines[i][1][2] - yoffsetb) + fyz * (self.box_lines[i][1][3] - zoffsetb)) * self.scale * eyedistance / (eyedistance + z1) + yoffset, (fxx * (self.box_lines[i][2][1] - xoffsetb) + fxy * (self.box_lines[i][2][2] - yoffsetb) + fxz * (self.box_lines[i][2][3] - zoffsetb)) * self.scale * eyedistance / (eyedistance + z2) + xoffset, (fyx * (self.box_lines[i][2][1] - xoffsetb) + fyy * (self.box_lines[i][2][2] - yoffsetb) + fyz * (self.box_lines[i][2][3] - zoffsetb)) * self.scale * eyedistance / (eyedistance + z2) + yoffset) else gc:drawLine((fxx * (self.box_lines[i][1][1] - xoffsetb) + fxy * (self.box_lines[i][1][2] - yoffsetb) + fxz * (self.box_lines[i][1][3] - zoffsetb)) * self.scale + xoffset, (fyx * (self.box_lines[i][1][1] - xoffsetb) + fyy * (self.box_lines[i][1][2] - yoffsetb) + fyz * (self.box_lines[i][1][3] - zoffsetb)) * self.scale + yoffset, (fxx * (self.box_lines[i][2][1] - xoffsetb) + fxy * (self.box_lines[i][2][2] - yoffsetb) + fxz * (self.box_lines[i][2][3] - zoffsetb)) * self.scale + xoffset, (fyx * (self.box_lines[i][2][1] - xoffsetb) + fyy * (self.box_lines[i][2][2] - yoffsetb) + fyz * (self.box_lines[i][2][3] - zoffsetb)) * self.scale + yoffset) end end end end function on.paint(gc) gc:setFont("sansserif", "r", 10) gc:setColorRGB(0, 0, 0) m_viewer:redraw(gc) end function on.resize(width, height) m_viewer:recalc(true) end function on.create() var.monitor("xmin") var.monitor("xmax") var.monitor("ymin") var.monitor("ymax") var.monitor("zmin") var.monitor("zmax") var.monitor("box") var.monitor("axes") var.monitor("xsplit") var.monitor("ysplit") var.monitor("f") var.monitor("orthogonal") m_viewer:recalc() end function on.activate() m_viewer:recalc() end function on.restore(state) m_viewer.scale = state["scale"] m_viewer.eyecoeff = state["eyecoeff"] m_viewer.thetax = state["thetax"] m_viewer.thetaz = state["thetaz"] end function on.save() data = {} data["scale"] = m_viewer.scale data["eyecoeff"] = m_viewer.eyecoeff data["thetax"] = m_viewer.thetax data["thetaz"] = m_viewer.thetaz return data end function on.charIn(char) if char == "*" then if m_viewer.scale < m_viewer:max_scale() then m_viewer.scale = m_viewer.scale * 1.2 platform.window:invalidate() end elseif char == "/" then if m_viewer.scale > m_viewer:min_scale() then m_viewer.scale = m_viewer.scale / 1.2 platform.window:invalidate() end elseif char == "+" then if m_viewer.eyecoeff > min_eyecoeff and not m_viewer.orthogonal then m_viewer.eyecoeff = m_viewer.eyecoeff / 1.15 platform.window:invalidate() end elseif char == "-" then if m_viewer.eyecoeff < max_eyecoeff and not m_viewer.orthogonal then m_viewer.eyecoeff = m_viewer.eyecoeff * 1.15 platform.window:invalidate() end else m_viewer:recalc(true) end end function on.arrowUp() m_viewer.thetax = m_viewer.thetax + math.pi / 24 platform.window:invalidate() end function on.arrowDown() m_viewer.thetax = m_viewer.thetax - math.pi / 24 platform.window:invalidate() end function on.arrowLeft() m_viewer.thetaz = m_viewer.thetaz + math.pi / 24 platform.window:invalidate() end function on.arrowRight() m_viewer.thetaz = m_viewer.thetaz - math.pi / 24 platform.window:invalidate() end function on.varChange(varlist) for _, v in ipairs(varlist) do if v == "xmin" then if type(var.recall(v)) == "nil" then return -3 elseif type(var.recall(v)) ~= "number" then return -2 end m_viewer:recalc(true) elseif v == "xmax" then if type(var.recall(v)) == "nil" then return -3 elseif type(var.recall(v)) ~= "number" then return -2 end m_viewer:recalc(true) elseif v == "ymin" then if type(var.recall(v)) == "nil" then return -3 elseif type(var.recall(v)) ~= "number" then return -2 end m_viewer:recalc(true) elseif v == "ymax" then if type(var.recall(v)) == "nil" then return -3 elseif type(var.recall(v)) ~= "number" then return -2 end m_viewer:recalc(true) elseif v == "zmin" then if type(var.recall(v)) == "nil" then return -3 elseif type(var.recall(v)) ~= "number" then return -2 end m_viewer:recalc(true) elseif v == "zmax" then if type(var.recall(v)) == "nil" then return -3 elseif type(var.recall(v)) ~= "number" then return -2 end m_viewer:recalc(true) elseif v == "axes" then if type(var.recall(v)) == "nil" then return -3 elseif type(var.recall(v)) ~= "boolean" then return -2 end elseif v == "box" then if type(var.recall(v)) == "nil" then return -3 elseif type(var.recall(v)) ~= "boolean" then return -2 end elseif v == "xsplit" then if type(var.recall(v)) == "nil" then return -3 elseif type(var.recall(v)) ~= "number" then return -2 elseif var.recall(v) < 0 then return -1 end m_viewer:recalc(true) elseif v == "ysplit" then if type(var.recall(v)) == "nil" then return -3 elseif type(var.recall(v)) ~= "number" then return -2 elseif var.recall(v) < 0 then return -1 end m_viewer:recalc(true) elseif v == "f" then m_viewer:recalc(true) elseif v == "orthogonal" then if type(var.recall(v)) == "nil" then return -3 elseif type(var.recall(v)) ~= "boolean" then return -2 end m_viewer:recalc(true) end end return 0 end m_viewer = Viewer() min_eyecoeff = 1 max_eyecoeff = 10