Created
May 20, 2013 10:40
-
-
Save loopspace/5611549 to your computer and use it in GitHub Desktop.
Library Miscellaneous Release v2 -A library of miscellaneous classes and functions.
This file contains hidden or 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
| Library Miscellaneous Tab Order Version: 2 | |
| ------------------------------ | |
| This file should not be included in the Codea project. | |
| #ChangeLog | |
| #Main | |
| #EuclideanPlane | |
| #ComplexPlane | |
| #Shape | |
| #ShapeElements | |
| #Shapes | |
| #Tracks |
This file contains hidden or 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
| --[[ | |
| ChangeLog | |
| ========= | |
| v2.0 Split into separate libraries: "Library Miscellaneous" is for | |
| things that don't fit elsewhere. | |
| v1.0 Converted to use toadkick's cmodule for importing | |
| and Briarfox's AutoGist for versionning. | |
| It needs toadkick's code from | |
| https://gist.github.com/apendley/5411561 | |
| tested with version 0.0.8 | |
| To use without AutoGist, comment out the lines in setup. | |
| --]] |
This file contains hidden or 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
| --[==[ | |
| -- Complex plane class | |
| -- Author: Andrew Stacey | |
| -- Website: http://www.math.ntnu.no/~stacey/HowDidIDoThat/iPad/Codea.html | |
| -- Licence: CC0 http://wiki.creativecommons.org/CC0 | |
| local Colour = unpack(cimport "Colour",nil) | |
| local Complex = cimport "Complex" | |
| cimport "ColourNames" | |
| cimport "Menu" | |
| cimport "Slider" | |
| cimport "NumberSpinner" | |
| cimport "Utilities" | |
| local ComplexPlane = class() | |
| function ComplexPlane:init(f,ui,t) | |
| Complex.precision = 1 | |
| self.o = vec2(WIDTH/2,HEIGHT/2) | |
| self.font = f | |
| self.points = {} | |
| self.bgcolour = Colour.readData("local", "bgcolour", Colour.x11.Bisque4) | |
| self.axescolour = Colour.readData("local", "axescolour",Colour.svg.Black) | |
| self.controlcolour = Colour.readData("local", "controlcolour", Colour.svg.SlateBlue) | |
| self.ptcolour = Colour.readData("local", "ptcolour", Colour.svg.Fuchsia) | |
| self.cptcolour = Colour.readData("local", "cptcolour", Colour.svg.Thistle) | |
| self.textcolour = Colour.readData("local", "textcolour", Colour.svg.Black) | |
| self.font:setColour(self.textcolour) | |
| self.scale = readLocalData("scale",HEIGHT/4) | |
| self.lh = f:lineheight() | |
| self.showvalue = Boolean.readData("local","showvalue",true) | |
| self.polar = Boolean.readData("local","polar",false) | |
| local m = ui:addMenu({title = "Complex Number", attach = true}) | |
| local om = ui:addMenu() | |
| local cm = ui:addMenu() | |
| local ctm = ui:addMenu() | |
| ctm:isChildOf(m) | |
| om:isChildOf(m) | |
| cm:isChildOf(m) | |
| m:addItem({ | |
| title = "Operation", | |
| action = function(x,y) | |
| om.active = not om.active | |
| om.x = x | |
| om.y = y | |
| end, | |
| highlight = function() | |
| return om.active | |
| end, | |
| deselect = function() | |
| om:deactivateDown() | |
| end | |
| }) | |
| m:addItem({ | |
| title = "Scale", | |
| action = function() | |
| ui:getParameter(self.scale, | |
| 1, | |
| HEIGHT, | |
| function(t) | |
| saveLocalData("scale",t) | |
| self.scale = t | |
| end | |
| ) | |
| return true | |
| end | |
| }) | |
| m:addItem({ | |
| title = "Single Point", | |
| action = function() | |
| self.singleton = not self.singleton | |
| Boolean.saveData("local", "singleton", self.singleton) | |
| return true | |
| end, | |
| highlight = function() | |
| return self.singleton | |
| end | |
| }) | |
| m:addItem({ | |
| title = "Constrain Control Point", | |
| action = function(x,y) | |
| ctm.active = not ctm.active | |
| ctm.x = x | |
| ctm.y = y | |
| end, | |
| highlight = function() | |
| return ctm.active | |
| end, | |
| deselect = function() | |
| ctm:deactivateDown() | |
| end | |
| }) | |
| m:addItem({ | |
| title = "Clear", | |
| action = function() | |
| self:clear() | |
| return true | |
| end | |
| }) | |
| m:addItem({ | |
| title = "Show Values", | |
| action = function() | |
| self.showvalue = not self.showvalue | |
| Boolean.saveData("local","showvalue",self.showvalue) | |
| return true | |
| end, | |
| highlight = function() | |
| return self.showvalue | |
| end | |
| }) | |
| m:addItem({ | |
| title = "Customise", | |
| action = function(x,y) | |
| cm.active = not cm.active | |
| cm.x = x | |
| cm.y = y | |
| end, | |
| highlight = function() | |
| return cm.active | |
| end, | |
| deselect = function() | |
| cm:deactivateDown() | |
| end | |
| }) | |
| for k,v in ipairs({ | |
| "Addition", | |
| "Multiplication", | |
| "Reciprocal", | |
| "Conjugate", | |
| "Minus", | |
| "Exponential" | |
| }) do | |
| om:addItem({ | |
| title = v, | |
| action = function() | |
| self.op = v | |
| return true | |
| end, | |
| highlight = function() | |
| return self.op == v | |
| end | |
| }) | |
| end | |
| om:addItem({ | |
| title = "Roots", | |
| action = function() | |
| self.op = "Roots" | |
| ui:getNumberSpinner({ | |
| action = function(t) | |
| t = tonumber(t) | |
| self.root = math.floor(math.abs(t)) | |
| return true | |
| end, | |
| maxdigits = 1, | |
| maxdecs = 0, | |
| allowSignChange = false | |
| } | |
| ) | |
| return true | |
| end, | |
| highlight = function() | |
| return self.op == "Roots" | |
| end | |
| }) | |
| cm:addItem({ | |
| title = "Point Size", | |
| action = function() | |
| ui:getParameter(self.radius, | |
| 1, | |
| 50, | |
| function(t) | |
| saveLocalData("radius",t) | |
| self.radius = t | |
| end | |
| ) | |
| return true | |
| end | |
| }) | |
| cm:addItem({ | |
| title = "Polar Form", | |
| action = function() | |
| self.polar = not self.polar | |
| Boolean.saveData("local","polar",self.polar) | |
| return true | |
| end, | |
| highlight = function() | |
| return self.polar | |
| end | |
| }) | |
| for k,v in ipairs({ | |
| {"Background Colour", "bgcolour"}, | |
| {"Axes Colour", "axescolour"}, | |
| {"Control Point Colour", "controlcolour"}, | |
| {"Point Colour", "ptcolour"}, | |
| {"Computed Point Colour", "cptcolour"} | |
| }) do | |
| cm:addItem({ | |
| title = v[1], | |
| action = function() | |
| ui:getColour("svg", | |
| function(c) | |
| self[v[2]]= c | |
| Colour.saveData("local",v[2],c) | |
| return true | |
| end | |
| ) | |
| return true | |
| end | |
| }) | |
| end | |
| cm:addItem({ | |
| title = "Text Colour", | |
| action = function() | |
| ui:getColour("svg", | |
| function(c) | |
| self.textcolour= c | |
| Colour.saveData("local","textcolour",c) | |
| self.font:setColour(self.textcolour) | |
| return true | |
| end | |
| ) | |
| return true | |
| end | |
| }) | |
| ctm:addItem({ | |
| title = "None", | |
| action = function() | |
| self.ctrlcond = function(z) | |
| return z | |
| end | |
| self.ctrlname = "None" | |
| return true | |
| end, | |
| highlight = function() | |
| return self.ctrlname == "None" | |
| end | |
| }) | |
| ctm:addItem({ | |
| title = "Real Axis", | |
| action = function() | |
| self.ctrlcond = function(z) | |
| z.z.y = 0 | |
| return z | |
| end | |
| self.ctrlname = "Real" | |
| return true | |
| end, | |
| highlight = function() | |
| return self.ctrlname == "Real" | |
| end | |
| }) | |
| ctm:addItem({ | |
| title = "Imaginary Axis", | |
| action = function() | |
| self.ctrlcond = function(z) | |
| z.z.x = 0 | |
| return z | |
| end | |
| self.ctrlname = "Imaginary" | |
| return true | |
| end, | |
| highlight = function() | |
| return self.ctrlname == "Imaginary" | |
| end | |
| }) | |
| ctm:addItem({ | |
| title = "Unit Circle", | |
| action = function() | |
| self.ctrlcond = function(z) | |
| if z:is_zero() then | |
| return Complex(1,0) | |
| else | |
| return z:normalise() | |
| end | |
| end | |
| self.ctrlname = "Unit" | |
| return true | |
| end, | |
| highlight = function() | |
| return self.ctrlname == "Unit" | |
| end | |
| }) | |
| ctm:addItem({ | |
| title = "Gaussian Integer", | |
| action = function() | |
| self.ctrlcond = function(z) | |
| z.z.x = math.floor(z.z.x + .5) | |
| z.z.y = math.floor(z.z.y + .5) | |
| return z | |
| end | |
| self.ctrlname = "Gauss" | |
| return true | |
| end, | |
| highlight = function() | |
| return self.ctrlname == "Gauss" | |
| end | |
| }) | |
| self.control = false | |
| self.controlpt = Complex(0,0) | |
| self.ctrlcond = function(z) return z end | |
| self.ctrlname = "None" | |
| self.radius = readLocalData("radius",10) | |
| self.controlr = readLocalData("controlr",20) | |
| self.root = 4 | |
| self.singleton = Boolean.readData("local", "singleton", false) | |
| t:pushHandler(self) | |
| ui:addHelp({ | |
| title = "Complex Plane", | |
| text = "Touch the screen to draw points, then select an operation from the menu to see the effect. " .. | |
| "For operations that require two numbers (such as addition), the second point is the larger control point. " .. | |
| "This can be moved by dragging it around the screen. " .. | |
| "Various options are available in the menus: " .. | |
| "It is possible to constrain the movement of the control point, " .. | |
| "to draw only one point rather than adding new ones (useful for roots), " .. | |
| "to scale the drawing area, " .. | |
| "and whether or not to show the values of the points (only those for the last point are shown)." | |
| } | |
| ) | |
| end | |
| function ComplexPlane:draw() | |
| local o = self.o | |
| local x | |
| local s = self.scale | |
| background(self.bgcolour) | |
| stroke(self.axescolour) | |
| strokeWidth(8) | |
| line(o.x,0,o.x,HEIGHT) | |
| line(0,o.y,WIDTH,o.y) | |
| strokeWidth(3) | |
| noFill() | |
| ellipseMode(RADIUS) | |
| ellipse(o.x,o.y,s) | |
| self.font:write("1",o.x+s,o.y+5) | |
| self.font:write("i",o.x+5,o.y + s) | |
| self.font:write("-1",o.x-s-24,o.y+5) | |
| self.font:write("-i",o.x+5,o.y - s - self.lh/2) | |
| fill(self.ptcolour) | |
| strokeWidth(-1) | |
| local r = self.radius | |
| local n | |
| for k,v in ipairs(self.points) do | |
| x = s * v.z + o | |
| ellipse(x.x,x.y,r) | |
| n = k | |
| end | |
| if self.op then | |
| fill(self.cptcolour) | |
| local t | |
| for k,v in ipairs(self.points) do | |
| t = self:operation(self.op,v) | |
| for l,w in ipairs(t) do | |
| x = s * w.z + o | |
| ellipse(x.x,x.y,r) | |
| end | |
| end | |
| end | |
| if self.control then | |
| fill(self.controlcolour) | |
| x = s * self.controlpt.z + o | |
| ellipse(x.x,x.y,self.controlr) | |
| end | |
| if self.showvalue and n then | |
| local z = self.points[n] | |
| x = s * z.z + o | |
| local st | |
| if self.polar then | |
| st = z:topolarstring() | |
| else | |
| st = z:tostring() | |
| end | |
| self.font:write(st,x.x,x.y) | |
| if self.op then | |
| local t = self:operation(self.op,z) | |
| for l,w in ipairs(t) do | |
| x = s * w.z + o | |
| if self.polar then | |
| st = w:topolarstring() | |
| else | |
| st = w:tostring() | |
| end | |
| self.font:write(st,x.x,x.y) | |
| end | |
| end | |
| if self.control then | |
| x = s * self.controlpt.z + o | |
| local st | |
| if self.polar then | |
| st = self.controlpt:topolarstring() | |
| else | |
| st = self.controlpt:tostring() | |
| end | |
| self.font:write(st,x.x,x.y) | |
| end | |
| end | |
| end | |
| function ComplexPlane:fullscreen(od,d) | |
| if d == STANDARD then | |
| self.o = vec2(375,HEIGHT/2) | |
| else | |
| self.o = vec2(512,HEIGHT/2) | |
| end | |
| end | |
| function ComplexPlane:clear() | |
| self.points = {} | |
| end | |
| function ComplexPlane:operation(t,v) | |
| if t == "Addition" then | |
| self.control = true | |
| return {v + self.controlpt} | |
| elseif t == "Multiplication" then | |
| self.control = true | |
| return {v * self.controlpt} | |
| elseif t == "Reciprocal" then | |
| self.control = false | |
| if not v:is_zero() then | |
| return {v:reciprocal()} | |
| else | |
| return {} | |
| end | |
| elseif t == "Minus" then | |
| self.control = false | |
| return {-v} | |
| elseif t == "Conjugate" then | |
| self.control = false | |
| return {v^""} | |
| elseif t == "Exponential" then | |
| self.control = true | |
| if not v:is_zero() then | |
| return {v^self.controlpt} | |
| else | |
| return {} | |
| end | |
| elseif t == "Roots" then | |
| self.control = false | |
| local s = {} | |
| local n = 1/self.root | |
| for i = 1,self.root do | |
| table.insert(s,v:power(n,i)) | |
| end | |
| return s | |
| end | |
| end | |
| function ComplexPlane:isTouchedBy(touch) | |
| return true | |
| end | |
| function ComplexPlane:processTouches(g) | |
| if g.updated then | |
| local z | |
| local o = self.o | |
| local s = self.scale | |
| local rr = (self.controlr/s)^2 | |
| for k,t in ipairs(g.touchesArr) do | |
| z = Complex((t.touch.x - o.x)/s,(t.touch.y - o.y)/s) | |
| if t.touch.state == BEGAN | |
| and not self.ctrltouch | |
| and self.controlpt:distSqr(z) < rr | |
| then | |
| self.ctrltouch = t.id | |
| end | |
| if t.id == self.ctrltouch then | |
| self.controlpt = self.ctrlcond(z) | |
| if t.touch.state == ENDED then | |
| self.ctrltouch = nil | |
| end | |
| elseif t.updated then | |
| if self.singleton then | |
| self.points = {z} | |
| else | |
| table.insert(self.points,z) | |
| end | |
| end | |
| end | |
| g:noted() | |
| end | |
| if g.type.ended then | |
| g:reset() | |
| end | |
| end | |
| function ComplexPlane:saveStyle(t) | |
| t = t or {} | |
| for k,v in ipairs { | |
| "bgcolour", | |
| "axescolour", | |
| "controlcolour", | |
| "ptcolour", | |
| "cptcolour", | |
| "textcolour", | |
| "scale", | |
| "showvalue", | |
| "polar", | |
| "singleton" | |
| } do | |
| t[v] = self[v] | |
| end | |
| return t | |
| end | |
| function ComplexPlane:restoreStyle(t) | |
| t = t or {} | |
| for k,v in ipairs { | |
| "bgcolour", | |
| "axescolour", | |
| "controlcolour", | |
| "ptcolour", | |
| "cptcolour", | |
| "textcolour", | |
| "scale", | |
| "showvalue", | |
| "polar", | |
| "singleton" | |
| } do | |
| self[v] = t[v] or self[v] | |
| end | |
| self.font:setColour(self.textcolour) | |
| end | |
| function ComplexPlane:getPoints(t) | |
| t = t or {} | |
| for k,v in ipairs(self.points) do | |
| table.insert(t,v) | |
| end | |
| return t | |
| end | |
| function ComplexPlane:setPoints(t) | |
| self.points = {} | |
| for k,v in ipairs(t) do | |
| table.insert(self.points,v) | |
| end | |
| end | |
| return ComplexPlane | |
| --]==] |
This file contains hidden or 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
| --[==[ | |
| -- EuclideanPlane | |
| local EuclideanPlane = class() | |
| local Font,Sentence = unpack(cimport "Font",nil) | |
| local Colour = unpack(cimport "Colour",nil) | |
| cimport "Matrix" | |
| function EuclideanPlane:init(t) | |
| t = t or {} | |
| local x = t.x or WIDTH/2 | |
| local y = t.y or HEIGHT/2 | |
| self.o = vec2(x,y) | |
| local width = t.width or WIDTH | |
| local height = t.height or HEIGHT | |
| self.matrix = t.matrix or Matrix({{1,0},{0,1}}) | |
| self.size = t.size or math.min(width,height)/2 | |
| self.scale = t.scale or 2 | |
| self.sf = self.size/self.scale | |
| self.colour = t.colour or Colour.svg.LightBlue | |
| self.acolour = t.axesColour or Colour.svg.Black | |
| self.pcolour = t.pointColour or Colour.svg.LightPink | |
| local fcolour = t.fontColour or Colour.svg.DarkSlateBlue | |
| local fontname = t.fontname or "HoeflerText-Italic" | |
| local fontsize = t.fontsize or 20 | |
| self.font = Font({name = fontname, size = fontsize}) | |
| self.font:setColour(fcolour) | |
| self.points = {} | |
| self.cpoints = {} | |
| self.lines = {} | |
| self.clines = {} | |
| self.npoints = 0 | |
| self.nlines = 0 | |
| self.lastpoint = nil | |
| self.lastline = {} | |
| self.psize = t.pointSize or 10 | |
| self.name = t.name | |
| self.xlabel = Sentence(self.font,"x") | |
| self.xlabel:setAnchor("south east") | |
| self.xlabel:setColour(fcolour) | |
| self.ylabel = Sentence(self.font,"y") | |
| self.ylabel:setAnchor("north west") | |
| self.ylabel:setColour(fcolour) | |
| if t.touchHandler then | |
| t.touchHandler:pushHandler(self) | |
| end | |
| self.active = true | |
| end | |
| function EuclideanPlane:draw() | |
| if not self.active then | |
| return | |
| end | |
| local w = self.size | |
| local r = self.psize | |
| local o = self.o | |
| local d = self.scale | |
| pushStyle() | |
| pushMatrix() | |
| resetStyle() | |
| resetMatrix() | |
| ortho(0,WIDTH,0,HEIGHT,2*r,-2*r) | |
| smooth() | |
| translate(o.x,o.y) | |
| noStroke() | |
| fill(self.colour) | |
| rectMode(RADIUS) | |
| rect(0,0,w,w) | |
| strokeWidth(4) | |
| stroke(self.acolour) | |
| line(0,-w,0,w) | |
| line(-w,0,w,0) | |
| self.xlabel:draw(w,0) | |
| self.ylabel:draw(0,w) | |
| --modelMatrix(self.matrix*modelMatrix()) | |
| fill(self.pcolour) | |
| noStroke() | |
| clip(o.x - w,o.y - w,2 * w, 2 * w) | |
| local u | |
| for k,v in ipairs(self.cpoints) do | |
| ellipse(v.x,v.y,r) | |
| end | |
| strokeWidth(4) | |
| stroke(self.pcolour) | |
| lineCapMode(PROJECT) | |
| for k,v in ipairs(self.clines) do | |
| line(v[1].x,v[1].y,v[2].x,v[2].y) | |
| end | |
| noClip() | |
| popStyle() | |
| popMatrix() | |
| end | |
| function EuclideanPlane:setMatrix(m) | |
| self.matrix = m | |
| self:recomputePoints() | |
| end | |
| function EuclideanPlane:setScaleFactor(s) | |
| self.sf = s | |
| self:recomputePoints() | |
| end | |
| function EuclideanPlane:isTouchedBy(touch) | |
| if not self.active then | |
| return false | |
| end | |
| if touch.x < self.o.x - self.size then | |
| return false | |
| end | |
| if touch.x > self.o.x + self.size then | |
| return false | |
| end | |
| if touch.y < self.o.y - self.size then | |
| return false | |
| end | |
| if touch.y > self.o.y + self.size then | |
| return false | |
| end | |
| return true | |
| end | |
| function EuclideanPlane:processTouches(g) | |
| if g.updated then | |
| if g.num == 1 then | |
| local z | |
| local o = self.o | |
| local s = self.sf | |
| local m = s*self.matrix | |
| local size = self.size | |
| local t = g.touchesArr[1] | |
| if t.updated then | |
| if not self.singleton | |
| and t.touch.state == MOVING | |
| and t.laststate == BEGAN then | |
| if t.touch.x > o.x - size | |
| and t.touch.x < o.x + size | |
| and t.touch.y > o.y - size | |
| and t.touch.y < o.y + size | |
| then | |
| z = Vector((t.touch.x - o.x)/s,(t.touch.y - o.y)/s) | |
| table.insert(self.points,z) | |
| table.insert(self.cpoints,m*z) | |
| self.npoints = self.npoints + 1 | |
| end | |
| end | |
| if t.touch.state ~= BEGAN then | |
| if t.touch.x > o.x - size | |
| and t.touch.x < o.x + size | |
| and t.touch.y > o.y - size | |
| and t.touch.y < o.y + size | |
| then | |
| z = Vector((t.touch.x - o.x)/s,(t.touch.y - o.y)/s) | |
| if self.singleton then | |
| self.points = {z} | |
| self.cpoints = {m*z} | |
| self.npoints = 1 | |
| else | |
| table.insert(self.points,z) | |
| table.insert(self.cpoints,m*z) | |
| self.npoints = self.npoints + 1 | |
| end | |
| self.lastpoint = z | |
| end | |
| end | |
| end | |
| elseif g.num == 2 then | |
| local za,zb | |
| local o = self.o | |
| local s = self.sf | |
| local m = s*self.matrix | |
| local ta = g.touchesArr[1] | |
| local tb = g.touchesArr[2] | |
| za = Vector((ta.touch.x - o.x)/s,(ta.touch.y - o.y)/s) | |
| zb = Vector((tb.touch.x - o.x)/s,(tb.touch.y - o.y)/s) | |
| if ta.updated or tb.updated then | |
| if self.singleton then | |
| self.lines = {{za,zb}} | |
| self.clines = {{m*za,m*zb}} | |
| else | |
| table.insert(self.lines,{za,zb}) | |
| table.insert(self.clines,{m*za,m*zb}) | |
| end | |
| end | |
| self.lastline = {za,zb} | |
| elseif g.num == 3 then | |
| if g.type.ended and g.type.tap then | |
| self:clear() | |
| end | |
| end | |
| g:noted() | |
| end | |
| if (g.type.ended and not g.type.tap) or g.type.finished then | |
| g:reset() | |
| end | |
| end | |
| function EuclideanPlane:clear() | |
| self.points = {} | |
| self.cpoints = {} | |
| self.npoints = 0 | |
| self.clines = {} | |
| self.lines = {} | |
| self.nlines = 0 | |
| self.lastpoint = nil | |
| self.lastline = {} | |
| end | |
| function EuclideanPlane:activate() | |
| self.active = true | |
| self:clear() | |
| end | |
| function EuclideanPlane:deactivate() | |
| self.active = false | |
| self:clear() | |
| end | |
| function EuclideanPlane:addPoint(z) | |
| local m = self.sf * self.matrix | |
| if z:linfty() <= self.scale then | |
| if self.singleton then | |
| self.points = {z} | |
| self.cpoints = {m*z} | |
| self.npoints = 1 | |
| else | |
| table.insert(self.points,z) | |
| table.insert(self.cpoints,m*z) | |
| self.npoints = self.npoints + 1 | |
| end | |
| self.lastpoint = z | |
| else | |
| self.lastpoint = nil | |
| end | |
| end | |
| function EuclideanPlane:recomputePoints() | |
| local t = {} | |
| local m = self.sf*self.matrix | |
| for k,v in ipairs(self.points) do | |
| table.insert(t,m*v) | |
| end | |
| self.cpoints = t | |
| t = {} | |
| for k,v in ipairs(self.lines) do | |
| table.insert(t,{m*v[1],m*v[2]}) | |
| end | |
| self.clines = t | |
| end | |
| return EuclideanPlane | |
| --]==] |
This file contains hidden or 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
| VERSION = 2.0 | |
| clearProjectData() | |
| -- DEBUG = true | |
| -- Use this function to perform your initial setup | |
| function setup() | |
| autogist = AutoGist("Library Miscellaneous","A library of miscellaneous classes and functions.",VERSION) | |
| autogist:backup(true) | |
| --displayMode(FULLSCREEN_NO_BUTTONS) | |
| cmodule "Library CModule" | |
| cimport "TestSuite" | |
| local Touches = cimport "Touch" | |
| local UI = cimport "UI" | |
| local Debug = cimport "Debug" | |
| local Playlist = cimport "Playlist" | |
| local Colour = unpack(cimport "Colour",nil) | |
| cimport "ColourNames" | |
| cimport "PictureBrowser" | |
| cimport "Menu" | |
| cimport "Keypad" | |
| cimport "Keyboard" | |
| local TextNode = cimport "TextNode" | |
| if cmodule.loaded("Menu") then | |
| print("Menu loaded") | |
| else | |
| print("Menu not loaded") | |
| end | |
| local View = cimport "View" | |
| local Quaternion = cimport "Quaternion" | |
| local Explosion = cimport "Explosion" | |
| --print(Quaternion(1,.56789,0,0)) | |
| touches = Touches() | |
| ui = UI(touches) | |
| debug = Debug({ui = ui}) | |
| ui:systemmenu() | |
| ui:helpmenu() | |
| ui:addMessage("This is a system message, hello everyone.") | |
| playlist = Playlist({ ui = ui}) | |
| testsuite.initialise({ui = ui}) | |
| --[[ | |
| colfn = function(col) | |
| print(col) | |
| ui:getColour(col,colfn) | |
| return true | |
| end | |
| colfn(Colour.svg.Black) | |
| --]] | |
| debug:log({ | |
| name = "Screen north west", | |
| message = function() local x,y = RectAnchorOf(Screen,"north west") return x .. ", " .. y end | |
| }) | |
| --debug:activate() | |
| ui:setPictureList({directory = "Documents", camera = true, filter = function(n,w,h) return math.min(w,h) > 500 end}) | |
| --ui:getPicture(function(i) img = i return true end) | |
| --print(USRotateCCW(vec2(1,0))) | |
| ui:declareKeyboard({name = "ArialMT", type = "fullqwerty"}) | |
| ui:useKeyboard("fullqwerty", | |
| function(k) print(k) return false end) | |
| print("Textnode") | |
| tn = TextNode({ | |
| pos = function() return WIDTH/2,800 end, | |
| anchor = "centre", | |
| --angle = 30, | |
| ui = ui, | |
| fit = true, | |
| maxHeight = HEIGHT, | |
| }) | |
| watch("tn.numlines") | |
| watch("dbug") | |
| touches:pushHandler(tn) | |
| -- | |
| print(PORTRAIT) | |
| print(PORTRAIT_UPSIDE_DOWN) | |
| print(PORTRAIT_ANY) | |
| print(LANDSCAPE_LEFT) | |
| print(LANDSCAPE_RIGHT) | |
| print(LANDSCAPE_ANY) | |
| print(ANY) | |
| --ui:setOrientation(PORTRAIT_UPSIDE_DOWN) | |
| -- ui:supportedOrientations(PORTRAIT_UPSIDE_DOWN) | |
| view = View(ui,touches) | |
| parameter.watch("view.baseRotation") | |
| parameter.watch("stuff[1]") | |
| parameter.watch("stack") | |
| shape = mesh() | |
| shape.texture = "Documents:Daniel at barnehage" | |
| local x,y,z = 1,1,1 | |
| shape.vertices = { | |
| vec3(x,0,0), | |
| vec3(0,y,0), | |
| vec3(0,0,z), | |
| vec3(0,0,0), | |
| vec3(0,y,0), | |
| vec3(0,0,z), | |
| vec3(x,0,0), | |
| vec3(0,0,0), | |
| vec3(0,0,z), | |
| vec3(x,0,0), | |
| vec3(0,y,0), | |
| vec3(0,0,0) | |
| } | |
| shape.colors = { | |
| Colour.svg.Red, | |
| Colour.svg.Green, | |
| Colour.svg.Blue, | |
| Colour.svg.White, | |
| Colour.svg.Green, | |
| Colour.svg.Blue, | |
| Colour.svg.Red, | |
| Colour.svg.White, | |
| Colour.svg.Blue, | |
| Colour.svg.Red, | |
| Colour.svg.Green, | |
| Colour.svg.White | |
| } | |
| view.eye = vec3(5,0,0) | |
| view.range = .25 | |
| perspective(40,WIDTH/HEIGHT) | |
| print(projectionMatrix()) | |
| camera(0,0,15,0,0,0,0,1,0) | |
| print(viewMatrix()) | |
| camera(0,0,-15,0,0,0,0,1,0) | |
| print(viewMatrix()) | |
| --watch("dbg") | |
| --ui:getCurve(vec4(0,0,3,-2),function(v) print(v) return true end) | |
| --ui:getColour(function(c) print(c) return true end) | |
| tw = {t = 0,s=0} | |
| ui:setTimer(5,function() tween(5,tw,{t = 1,s = 0}) return true end) | |
| ui:setTimer(12,function() tween(5,tw,{t = 1,s = 1}) return true end) | |
| ui:setTimer(18,function() tw = {t = 0,s = 1} return true end) | |
| ui:setTimer(19,function() tween(5,tw,{t = 1,s = 0}) return true end) | |
| print(Quaternion.Rotation(1,3,4,5)) | |
| parameter.watch("vm") | |
| parameter.watch("qt") | |
| qslp = Quaternion.unit():make_slerp(Quaternion(1,0,0,0)) | |
| qlp = Quaternion.unit():make_lerp(Quaternion(-1,0,0,0)) | |
| explosion = Explosion({ | |
| image = "Cargo Bot:Codea Icon", | |
| trails = true, | |
| centre = vec2(WIDTH/2,HEIGHT/2) | |
| }) | |
| --explosion:activate(1,5) | |
| --ui:addNotice({text = "Watch carefully"}) | |
| ui:getNumber(function(n) print(n) end) | |
| end | |
| -- This function gets called once every frame | |
| function draw() | |
| -- process touches and taps | |
| touches:draw() | |
| background(34, 47, 53, 255) | |
| --testsuite.draw() | |
| --playlist:draw() | |
| --[[ | |
| fill(255, 255, 255, 255) | |
| pushMatrix() | |
| translate(WIDTH/2,HEIGHT/2) | |
| rotate(45) | |
| text("hello",0,0) | |
| popMatrix() | |
| --]] | |
| if img then | |
| local w,h = img.width,img.height | |
| local asp = math.min(WIDTH/w,HEIGHT/h,1) | |
| --sprite(img,WIDTH/2,HEIGHT/2,w*asp,h*asp) | |
| end | |
| tn:draw() | |
| pushMatrix() | |
| view:draw() | |
| vm = viewMatrix() | |
| qt = qslp(tw.t) | |
| qt = qt*qlp(tw.s) | |
| --modelMatrix(qt:tomatrix()) | |
| --perspective(40,WIDTH/HEIGHT) | |
| --camera(10,10,10,0,0,0,0,1,0) | |
| shape:draw() | |
| popMatrix() | |
| resetMatrix() | |
| viewMatrix(matrix()) | |
| ortho() | |
| explosion:draw() | |
| ui:draw() | |
| debug:draw() | |
| touches:show() | |
| end | |
| function touched(touch) | |
| touches:addTouch(touch) | |
| end | |
| function orientationChanged(o) | |
| if ui then | |
| ui:orientationChanged(o) | |
| end | |
| end | |
| function fullscreen() | |
| end | |
| function reset() | |
| end |
This file contains hidden or 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
| --[==[ | |
| -- 3D Shapes | |
| -- Author: Andrew Stacey | |
| -- Website: http://www.math.ntnu.no/~stacey/HowDidIDoThat/iPad/Codea.html | |
| -- Licence: CC0 http://wiki.creativecommons.org/CC0 | |
| --[[ | |
| A "Shape" is basically a list of vertices and edges in 3-space which | |
| is projected onto the iPad screen. | |
| In actual fact, it is slightly more complicated than that as there are | |
| various supplementary pieces that we need such as default properties | |
| for the edges and vertices. | |
| Perhaps the key extra piece of information is whether to use the more | |
| accurate method for determining which edge or vertex is on top and | |
| redrawing the one on top using an appropriate clip if required. This | |
| solves the problem that ordering edges according to which is "on top" | |
| (as seen from the eye) is not a transitive relation. However, it is | |
| slower than a crude ordering which ignores this subtlety. | |
| --]] | |
| local Shape = class() | |
| View = cimport "View" | |
| Vec3 = cimport "Vec3" | |
| --[[ | |
| This is our initialiser function. As well as setting up a few | |
| variables, we define a menu for changing various properties of our | |
| object. Our arguments are the surrounding space and the touch handler. | |
| --]] | |
| function Shape:init(ui,t) | |
| self.vertices = {} -- vertices | |
| self.edges = {} -- edges | |
| self.faces = {} | |
| self.triangles = {} | |
| self.facemesh = mesh() | |
| self.surface = false | |
| self.textures = {} | |
| self.edgesBetween = {} -- edges specified by listing the endpoints | |
| t:pushHandler(self) | |
| self.space = View(ui,t) | |
| self.defaults = {} | |
| self.defaults.vertex = {radius = .1, colour = Colour.svg.Red} | |
| self.defaults.edge = {thickness = 10, colour = Colour.svg.Yellow} | |
| self.defaults.face = {colour = Colour.svg.Red} | |
| self.actives = {} | |
| self.edit = false | |
| self.ClipSize = 10 | |
| self.checkIntersections = false | |
| self.shapeMenu = self.space.ui:addMenu() | |
| for k,v in ipairs(preDefinedShapes) do | |
| self.shapeMenu:addItem({ | |
| title = v, | |
| action = function() | |
| self:loadShape(v) | |
| return true | |
| end, | |
| highlight = function() | |
| return v == self.shapeName | |
| end | |
| }) | |
| end | |
| self.savedShapes = readProjectData("Shapes") or "" | |
| local a = self.savedShapes | |
| while a ~= "" do | |
| local i,j = string.find(a,";") | |
| local v = string.sub(a,1,i - 1) | |
| a = string.sub(a,i + 1,-1) | |
| self.shapeMenu:addItem({ | |
| title = v, | |
| action = function() | |
| self:loadShape(v) | |
| return true | |
| end, | |
| highlight = function() | |
| return v == self.shapeName | |
| end | |
| }) | |
| end | |
| self.shapeMenu:addItem({ | |
| title = "Saved Shape", | |
| action = function() | |
| self:loadShape() | |
| return true | |
| end | |
| }) | |
| local m | |
| m = self.space.ui:addMenu({title = "Shape", attach = true}) | |
| self.shapeMenu.parent = m | |
| m:addItem({ | |
| title = "Apply Spatial Transformation", | |
| action = function() | |
| self:applySpatial() | |
| self.space:restoreInitials() | |
| return true | |
| end | |
| }) | |
| m:addItem({ | |
| title = "Apply Default Properties", | |
| action = function() | |
| self:applyDefaults() | |
| return true | |
| end | |
| }) | |
| m:addItem({ | |
| title = "Load Shape", | |
| action = function(x,y) | |
| self.clearOnLoad = true | |
| if self.shapeMenu.active then | |
| self.shapeMenu:deactivateDown() | |
| else | |
| self.shapeMenu:activate() | |
| self.shapeMenu.x = x | |
| self.shapeMenu.y = y | |
| end | |
| end, | |
| deselect = function() | |
| self.shapeMenu:deactivateDown() | |
| end, | |
| highlight = function() | |
| return self.shapeMenu.active and self.clearOnLoad | |
| end | |
| }) | |
| m:addItem({ | |
| title = "Save Shape", | |
| action = function() | |
| self.space.ui:getText( | |
| function(t) | |
| local s | |
| s = self:toString() | |
| self.savedShapes = self.savedShapes .. t .. ";" | |
| saveProjectData("Shapes",self.savedShapes) | |
| saveProjectData("Shape " .. t,s) | |
| self.space.ui:addMessage("Shape " .. t .. " saved") | |
| self.shapeMenu:addItem({ | |
| title = t, | |
| action = function() | |
| self:loadShape(t) | |
| return true | |
| end, | |
| highlight = function() | |
| return t == self.shapeName | |
| end | |
| }) | |
| return true | |
| end | |
| ) | |
| return true | |
| end | |
| }) | |
| m:addItem({ | |
| title = "Add Shape", | |
| action = function(x,y) | |
| self.clearOnLoad = false | |
| if self.shapeMenu.active then | |
| self.shapeMenu:deactivateDown() | |
| else | |
| self.shapeMenu:activate() | |
| self.shapeMenu.x = x | |
| self.shapeMenu.y = y | |
| end | |
| end, | |
| deselect = function() | |
| self.shapeMenu:deactivateDown() | |
| end, | |
| highlight = function() | |
| return self.shapeMenu.active and not self.clearOnLoad | |
| end | |
| }) | |
| m:addItem({ | |
| title = "Edit Shape", | |
| action = function() | |
| self.edit = not self.edit | |
| return true | |
| end, | |
| highlight = function() | |
| return self.edit | |
| end}) | |
| m:addItem({ | |
| title = "Delete selected vertices", | |
| action = function() | |
| self:delActiveVertices() | |
| return true | |
| end | |
| }) | |
| m:addItem({ | |
| title = "Colour Vertices", | |
| action = function() | |
| self.space.ui:getColour("svg", | |
| function(c) | |
| self:setParameter("vertex","colour",c) | |
| return true | |
| end | |
| ) | |
| return true | |
| end | |
| }) | |
| m:addItem({ | |
| title = "Vertex Radii", | |
| action = function() | |
| self.space.ui:getParameter( | |
| self.defaults.vertex.radius, | |
| .05, | |
| .3, | |
| function(t) | |
| self:setParameter("vertex","radius",t) | |
| return true | |
| end, | |
| function(t) | |
| self:setParameter("vertex","radius",t) | |
| self:edgesRecalc() | |
| return true | |
| end | |
| ) | |
| return true | |
| end | |
| }) | |
| m:addItem({ | |
| title = "Colour Edges", | |
| action = function() | |
| self.space.ui:getColour("svg", | |
| function(c) | |
| self:setParameter("edge","colour",c) | |
| return true | |
| end | |
| ) | |
| return true | |
| end | |
| }) | |
| m:addItem({ | |
| title = "Edge Thickness", | |
| action = function() | |
| self.space.ui:getParameter( | |
| self.defaults.edge.thickness, | |
| 5, | |
| 15, | |
| function(t) | |
| self:setParameter("edge","thickness",t) | |
| return true | |
| end, | |
| function(t) | |
| self:setParameter("edge","thickness",t) | |
| return true | |
| end | |
| ) | |
| return true | |
| end | |
| }) | |
| m:addItem({ | |
| title = "Use Intersection algorithm", | |
| action = function() | |
| self.checkIntersections = not self.checkIntersections | |
| return true | |
| end, | |
| highlight = function() | |
| return self.checkIntersections | |
| end}) | |
| ui:addHelp({ | |
| title = "Shape", | |
| text = "The shape refers to the object actually shown. There are two types of shape: ball-and-stick models and surfaces. The Shape menu gives you control over various aspects of the shapes, though not all options make sense for both types of shape." | |
| }) | |
| end | |
| function Shape:setTextureByName(n) | |
| if self.textures[n] then | |
| self.facemesh.texture = self.textures[n] | |
| self.hasTexture = false | |
| return true | |
| else | |
| return false | |
| end | |
| end | |
| function Shape:setTexture(i,n) | |
| if n then | |
| self.textures[n] = i | |
| end | |
| self.facemesh.texture = i | |
| self.hasTexture = true | |
| end | |
| function Shape:unsetTexture() | |
| self.facemesh.texture = nil | |
| end | |
| --[[ | |
| Clear the shape. | |
| --]] | |
| function Shape:clear() | |
| self.vertices = {} -- vertices | |
| self.edges = {} -- edges | |
| self.faces = {} | |
| self.triangles = {} | |
| self.facemesh = mesh() | |
| self.surface = false | |
| self.faceted = false | |
| self.hasTexture = false | |
| self.edgesBetween = {} -- edges specified by listing the endpoints | |
| self.actives = {} | |
| self.edit = false | |
| self.checkIntersections = false | |
| end | |
| --[[ | |
| Load a shape from either a function or the project data, possibly | |
| clearing the current shape first. | |
| --]] | |
| function Shape:loadShape(t) | |
| local s,a | |
| if self.clearOnLoad then | |
| a = " loaded" | |
| else | |
| a = " added" | |
| end | |
| if Shape[t] then | |
| if self.clearOnLoad then | |
| self:clear() | |
| end | |
| Shape[t](self) | |
| self.space.ui:addMessage("Shape " .. t .. a) | |
| elseif t then | |
| s = readProjectData("Shape " .. t) | |
| if s then | |
| if self.clearOnLoad then | |
| self:clear() | |
| end | |
| self:loadShapeFromData(s) | |
| self.space.ui:addMessage("Shape " .. t .. a) | |
| end | |
| else | |
| self.space.ui:getText( | |
| function(str) | |
| self:loadShape(str) | |
| return true | |
| end | |
| ) | |
| end | |
| end | |
| --[[ | |
| Set the default properties for the vertices. | |
| --]] | |
| function Shape:setVertexDefaults(r,c,v) | |
| self.defaults.vertex.radius = r | |
| self.defaults.vertex.colour = c | |
| self.defaults.vertex.valency = v | |
| self.ClipSize = math.max(self.ClipSize,2 * r * self.space.intScale * self.space.extScale) | |
| end | |
| --[[ | |
| Set the default properties for the edges. | |
| --]] | |
| function Shape:setEdgeDefaults(t,c) | |
| self.defaults.edge.thickness = t | |
| self.defaults.edge.colour = c | |
| self.ClipSize = math.max(self.ClipSize,2 * t) | |
| end | |
| --[[ | |
| This applies the default properties to the vertices and edges, meaning | |
| that changing the defaults will not change the appearance of the | |
| vertices and edges. This is particularly useful when merging shapes. | |
| --]] | |
| function Shape:applyDefaults() | |
| for k,v in pairs(self.vertices) do | |
| if not v.radius then | |
| v.radius = self.defaults.vertex.radius | |
| end | |
| if not v.colour then | |
| v.colour = self.defaults.vertex.colour | |
| end | |
| if not v.valency then | |
| v.valency = self.defaults.vertex.valency | |
| end | |
| end | |
| for k,v in pairs(self.edges) do | |
| if not v.thickness then | |
| v.thickness = self.defaults.edge.thickness | |
| end | |
| if not v.colour then | |
| v.colour = self.defaults.edge.colour | |
| end | |
| end | |
| end | |
| --[[ | |
| This sets a parameter (such as colour) for all the selected vertices | |
| or edges (and edge is selected if its two end vertices are selected). | |
| If none are selected then it changes the defaults. | |
| --]] | |
| function Shape:setParameter(r,s,t) | |
| local a = {} | |
| local b = false | |
| for k,v in ipairs(self.vertices) do | |
| if v.active then | |
| table.insert(a,v) | |
| b = true | |
| end | |
| end | |
| if b then | |
| if r == "vertex" then | |
| for k,v in ipairs(a) do | |
| v[s] = t | |
| end | |
| elseif r == "edge" then | |
| for k,e in ipairs(self.edges) do | |
| if e.head.active and e.tail.active then | |
| e[s] = t | |
| end | |
| end | |
| end | |
| else | |
| self.defaults[r][s] = t | |
| end | |
| end | |
| --[[ | |
| The following functions apply transformations to the shape so that it | |
| updates the actual positions of the pieces. There are various | |
| transformations that can be applies: scaling, translation, and | |
| rotation, and various functions for applying one or other of them. | |
| The last one applies the current spatial transformation. | |
| --]] | |
| function Shape:applyTransformation(t) | |
| if type(t) == "number" then | |
| self:applyScale(t) | |
| elseif type(t) == "table" then | |
| if t:is_a(Vec3) then | |
| self:applyTranslation(t) | |
| elseif t:is_a(Quaternion) then | |
| self:applyRotation(t) | |
| end | |
| end | |
| end | |
| function Shape:applyScale(s) | |
| for k,v in pairs(self.vertices) do | |
| v.coordinate = s * v.coordinate | |
| if v.radius then | |
| v.radius = s * v.radius | |
| end | |
| end | |
| self:edgesRecalc() | |
| end | |
| function Shape:applyRotation(q) | |
| for k,v in pairs(self.vertices) do | |
| v.coordinate = v.coordinate^q | |
| end | |
| for k,v in pairs(self.edges) do | |
| v.headpt = v.headpt^q | |
| v.tailpt = v.tailpt^q | |
| end | |
| end | |
| function Shape:applyTranslation(u) | |
| for k,v in pairs(self.vertices) do | |
| v.coordinate = v.coordinate + u | |
| end | |
| for k,v in pairs(self.edges) do | |
| v.headpt = v.headpt + u | |
| v.tailpt = v.tailpt + u | |
| end | |
| end | |
| function Shape:applySRT(s,q,u) | |
| for k,v in pairs(self.vertices) do | |
| v.coordinate = s * v.coordinate^q + u | |
| end | |
| self:edgesRecalc() | |
| end | |
| function Shape:applySpatial() | |
| for k,v in pairs(self.vertices) do | |
| v.coordinate = self.space:applyIntTransformation(v.coordinate) | |
| end | |
| self:edgesRecalc() | |
| end | |
| --[[ | |
| This sets our set of vertices from a table of vertices. | |
| --]] | |
| function Shape:setVertices(vv) | |
| self.vertices = vv | |
| for k,v in pairs(self.vertices) do | |
| v.shape = self | |
| end | |
| end | |
| --[[ | |
| It is allowed to specify the edges by saying which vertices they go between. | |
| --]] | |
| function Shape:setEdgesBetween(e) | |
| self.edgesBetween = e | |
| end | |
| --[[ | |
| This is for adding a vertex to the current shape. | |
| --]] | |
| function Shape:addVertex(v) | |
| -- check if v is a Vec3 or not | |
| table.insert(self.vertices,v) | |
| v.shape = self | |
| end | |
| --[[ | |
| This is for adding an edge to the current shape. | |
| --]] | |
| function Shape:addEdge(e) | |
| -- check if e is an edge or not | |
| table.insert(self.edges,e) | |
| e.shape = self | |
| end | |
| --[[ | |
| This is for adding an edge by specifying which vertices it goes between. | |
| --]] | |
| function Shape:addEdgeBetween(a,b) | |
| table.insert(self.edgesBetween,{a,b}) | |
| end | |
| --[[ | |
| It is possible to define a shape by specifying a set of vertices with | |
| valencies. This function computes the edges according to those | |
| valencies by iterating over the set of vertices and choosing, for | |
| each, the n closest other vertices with unfulfilled valencies, where n | |
| is the number of unfulfilled valencies of the current vertex. | |
| --]] | |
| function Shape:addEdgesByValency() | |
| for k,v in ipairs(self.vertices) do | |
| if not v.valency then | |
| v.valency = self.defaults.vertex.valency | |
| for l,e in pairs(v.edges) do | |
| v.valency = v.valency - 1 | |
| end | |
| end | |
| end | |
| for k,v in ipairs(self.vertices) do | |
| if v.valency > 0 then | |
| local vv = {} | |
| for l,u in ipairs(self.vertices) do | |
| if l > k then | |
| table.insert(vv,{u,u.coordinate:subtract(v.coordinate):lenSqr()}) | |
| end | |
| end | |
| table.sort(vv, function(a,b) return a[2] < b[2] end) | |
| local i = 1 | |
| repeat | |
| if not vv[i] then | |
| break | |
| end | |
| if vv[i][1].valency > 0 then | |
| local e = Edge(v,vv[i][1],self) | |
| e:Shorten() | |
| table.insert(self.edges,e) | |
| i = i + 1 | |
| end | |
| until v.valency == 0 | |
| end | |
| end | |
| end | |
| --[[ | |
| This function generates the family of edges from the "edges between" data. | |
| --]] | |
| function Shape:GenerateEdges() | |
| local u,w,e | |
| for k,v in pairs(self.edgesBetween) do | |
| u = self.vertices[v[1] + 1] | |
| w = self.vertices[v[2] + 1] | |
| if v[3] then | |
| e = Edge(u,w,self,unpack(v[3])) | |
| else | |
| e = Edge(u,w,self) | |
| end | |
| e:Shorten() | |
| table.insert(self.edges,e) | |
| end | |
| end | |
| --[[ | |
| When vertices are moved, or their radii changed, it is necessary to | |
| recompute the end points of the edges. This function does that. | |
| --]] | |
| function Shape:edgesRecalc() | |
| for k,v in pairs(self.edges) do | |
| v:Shorten() | |
| end | |
| end | |
| --[[ | |
| This adds a face to the shape, checking if the verties are already there. | |
| --]] | |
| function Shape:addFace(f) | |
| table.insert(self.faces,f) | |
| for k,v in ipairs (f.vertices) do | |
| if not v.shape then | |
| self:addVertex(v) | |
| end | |
| end | |
| for k,v in ipairs(f.triangles) do | |
| table.insert(self.triangles,v) | |
| end | |
| end | |
| --[[ | |
| This is our draw function. First, we project the vertices and edges | |
| onto the screen. Then we order them by distance from the eye (for | |
| edges, this is the furthest point from the eye). This gives a | |
| reasonable ordering and we draw them in that order. If the | |
| "checkIntersections" flag is set then when each piece is rendered, we | |
| check to see if any of the edges underneath it should really have been | |
| drawn on top. If so, we redraw the underneath edge clipped to near | |
| the intersection. | |
| --]] | |
| function Shape:draw() | |
| self.space:draw() | |
| local d = {} | |
| ellipseMode(RADIUS) | |
| for k,v in pairs(self.vertices) do | |
| v:Project() | |
| table.insert(d,v) | |
| if not self.edit then | |
| v.active = false | |
| end | |
| end | |
| if self.surface then | |
| local f = {} | |
| for k,v in pairs(self.faces) do | |
| v:Project() | |
| end | |
| for k,v in pairs(self.triangles) do | |
| table.insert(f,v) | |
| end | |
| table.sort(f,Shape.byLevel) | |
| local tv = {} | |
| local tt = {} | |
| local ft = {} | |
| local fc = {} | |
| local ftex = {} | |
| local n,c,nv | |
| for k,v in ipairs(f) do | |
| tv = v:getVertices() | |
| if self.hasTexture then | |
| tt = v:getTexCoords() | |
| end | |
| n = self.space:applyIntDirTransformation(v.normal) | |
| for i = 1,3 do | |
| table.insert(ft,tv[i].projection) | |
| if self.hasTexture then | |
| table.insert(ftex,tt[i]) | |
| end | |
| if self.faceted then | |
| nv = n | |
| else | |
| nv = tv[i].pnormal or n | |
| end | |
| if nv:is_zero() then | |
| table.insert(fc,Colour.transparent) | |
| else | |
| nv = nv:normalise() | |
| c = 40*nv:dot(self.space.light) + 50 | |
| if v.colour then | |
| c = Colour.shade(v.colour,c) | |
| else | |
| c = Colour.shade(self.defaults.face.colour,c) | |
| end | |
| table.insert(fc,c) | |
| end | |
| end | |
| end | |
| if self.hasTexture then | |
| self.facemesh.texCoords = ftex | |
| end | |
| self.facemesh.vertices = ft | |
| self.facemesh.colors = fc | |
| --pushStyle() | |
| --fill(Colour.svg.White) | |
| self.facemesh:draw() | |
| --popStyle() | |
| else | |
| for k,v in pairs(self.edges) do | |
| v:Project() | |
| table.insert(d,v) | |
| end | |
| table.sort(d,Shape.byLevel) | |
| local de = {} | |
| local ne = {} | |
| for k,v in ipairs(d) do | |
| v:draw() | |
| if self.checkIntersections then | |
| ne = {} | |
| for kk,vv in ipairs(de) do | |
| if vv.mlevel > v.level then | |
| if vv:isOver(v) then | |
| local z = vv:Intersect(v) + self.space.origin | |
| -- This is for debugging the overlaps | |
| if DEBUG then | |
| pushMatrix() | |
| pushStyle() | |
| resetMatrix() | |
| resetStyle() | |
| noStroke() | |
| ellipse(z.x,z.y,20) | |
| popStyle() | |
| popMatrix() | |
| end | |
| clip(z.x - self.ClipSize/2, | |
| z.y - self.ClipSize/2, | |
| self.ClipSize, | |
| self.ClipSize) | |
| vv:draw() | |
| noClip() | |
| end | |
| table.insert(ne,vv) | |
| end | |
| end | |
| de = ne | |
| if v:is_a(Edge) then | |
| table.insert(de,v) | |
| end | |
| end | |
| end | |
| end | |
| end | |
| --[[ | |
| This is an auxilliary function for sorting vertices and edges. | |
| --]] | |
| function Shape.byLevel(a,b) | |
| return a.level < b.level | |
| end | |
| --[[ | |
| The shape is touched if we are in edit mode. First we check if one of | |
| the vertices has been touched, if not the shape itself claims the | |
| touch. | |
| --]] | |
| function Shape:isTouchedBy(touch) | |
| self.touched = nil | |
| if self.edit then | |
| for k,v in pairs(self.vertices) do | |
| if v:isTouchedBy(touch) then | |
| self.touched = v | |
| return true | |
| end | |
| end | |
| return true | |
| end | |
| end | |
| --[[ | |
| We let all vertices see the touch, even if they have not been touched | |
| themselves, as all active vertices react to touches. If none have | |
| been touched, the shape adds a new vertex at the touch point. | |
| --]] | |
| function Shape:processTouches(g) | |
| if self.touched then | |
| for k,v in pairs(self.vertices) do | |
| v:processTouches(g) | |
| end | |
| else | |
| if g.updated then | |
| self:addDataFrom(g) | |
| end | |
| end | |
| if g.type.finished then | |
| g:reset() | |
| else | |
| g:noted() | |
| end | |
| end | |
| --[[ | |
| This is the function that adds the vertex from the data given by the | |
| gesture. | |
| --]] | |
| function Shape:addDataFrom(g) | |
| if g.type.tap and g.type.ended then | |
| local v,q,t | |
| t = g.touchesArr[1] | |
| v = vec2(t.touch.x,t.touch.y) | |
| v = v - self.space.origin - self.space.extTranslate | |
| v = Vec3(v.x,v.y,0) | |
| v = v / (self.space.intScale * self.space.extScale) | |
| v = v - self.space.intTranslate | |
| q = self.space.rotation | |
| q = q^"" | |
| v = v^q | |
| self:addVertex(Vertex(v)) | |
| end | |
| end | |
| --[[ | |
| This deletes all active vertices and their edges. | |
| --]] | |
| function Shape:delActiveVertices() | |
| for k,e in pairs(self.edges) do | |
| if e.head.active or e.tail.active then | |
| self.edges[k] = nil | |
| e:destroy() | |
| end | |
| end | |
| for k,v in pairs(self.vertices) do | |
| if v.active then | |
| self.vertices[k] = nil | |
| end | |
| end | |
| end | |
| --[[ | |
| This toggles edges between active vertices. | |
| --]] | |
| function Shape:toggleVertices(v) | |
| local a = {} | |
| local r = {} | |
| for k,u in pairs(self.vertices) do | |
| if u.active and u ~= v then | |
| a[u] = true | |
| end | |
| end | |
| for k,e in pairs(v.edges) do | |
| w = e:getVertex(v) | |
| if w.active then | |
| r[e] = true | |
| a[w] = nil | |
| end | |
| end | |
| for k,e in pairs(self.edges) do | |
| if r[e] then | |
| self.edges[k] = nil | |
| e:destroy() | |
| end | |
| end | |
| for k,u in pairs(a) do | |
| if u then | |
| e = Edge(k,v,self) | |
| e:Shorten() | |
| table.insert(self.edges,e) | |
| end | |
| end | |
| end | |
| --[[ | |
| This renders the shape data to a string which can be saved as project | |
| data and reconstructed later if so desired. | |
| --]] | |
| function Shape:toString() | |
| local s,t,n | |
| s = "" | |
| t = {} | |
| n = 0 | |
| for k,v in ipairs(self.vertices) do | |
| s = s .. "Vertex:" .. v:toString() .. ";" | |
| n = n + 1 | |
| t[v] = n | |
| end | |
| for k,e in pairs(self.edges) do | |
| s = s .. "Edge:" .. t[e.head] .. "," .. t[e.tail] .. "," .. e:toString() .. ";" | |
| end | |
| return s | |
| end | |
| --[[ | |
| This reconstructs a shape from its serialised data. | |
| --]] | |
| function Shape:loadShapeFromData(s) | |
| local v = {} | |
| local e = {} | |
| local i,j,a,b,c,p,q | |
| while s ~= "" do | |
| i,j = string.find(s,";") | |
| a = string.sub(s,1,i-1) | |
| s = string.sub(s,i+1,-1) | |
| i,j = string.find(a,":") | |
| b = string.sub(a,1,i-1) | |
| a = string.sub(a,i+1,-1) | |
| if b == "Vertex" then | |
| c = Vertex.fromString(a) | |
| table.insert(v,c) | |
| elseif b == "Edge" then | |
| i,j = string.find(a,",") | |
| p = string.sub(a,1,i-1) | |
| a = string.sub(a,i+1,-1) | |
| p = tonumber(p) | |
| i,j = string.find(a,",") | |
| q = string.sub(a,1,i-1) | |
| a = string.sub(a,i+1,-1) | |
| q = tonumber(q) | |
| c = Edge(v[p],v[q]) | |
| c:setProperties(a) | |
| table.insert(e,c) | |
| end | |
| end | |
| for k,u in ipairs(v) do | |
| self:addVertex(u) | |
| end | |
| for k,u in ipairs(e) do | |
| self:addEdge(u) | |
| end | |
| self:edgesRecalc() | |
| end | |
| return Shape | |
| --]==] |
This file contains hidden or 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
| --[==[ | |
| -- Shape elements | |
| --[[ | |
| The Vertex class is for a vertex of a shape. Our initial data is our | |
| coordinate (either a Vec3 object or a triple of numbers) and | |
| optionally a radius and colour. | |
| --]] | |
| local Vertex = class() | |
| local Vec3 = cimport "Vec3" | |
| function Vertex:init(...) | |
| local a | |
| -- coordinate | |
| -- colour | |
| -- radius | |
| -- projected coordinate | |
| -- scale factor for radius | |
| -- distance from eye | |
| -- surrounding space | |
| -- edges ending at this vertex | |
| -- valency | |
| if type(arg[1]) == "table" then | |
| if arg[1]:is_a(Vec3) then | |
| self.coordinate = arg[1] | |
| else | |
| print("Expected a Vec3") | |
| return false | |
| end | |
| arg[1] = nil | |
| elseif type(arg[1]) == "userdata" then | |
| if arg[1].x then | |
| -- hopefully a vec3 | |
| self.coordinate = Vec3(arg[1]) | |
| end | |
| arg[1] = nil | |
| elseif type(arg[1]) == "number" then | |
| self.coordinate = Vec3(arg[1],arg[2],arg[3]) | |
| arg[1] = nil | |
| arg[2] = nil | |
| arg[3] = nil | |
| end | |
| for k,v in pairs(arg) do | |
| if type(k) == "number" then | |
| if type(v) == "number" then | |
| self.radius = v | |
| elseif type(v) == "table" then | |
| if v:is_a(Space) then | |
| self.space = v | |
| else | |
| print("Expecting a colour") | |
| return false | |
| end | |
| elseif type(v) == "userdata" then | |
| if v.r then | |
| self.colour = v | |
| else | |
| print("Expecting a colour") | |
| return false | |
| end | |
| end | |
| end | |
| end | |
| self.edges = {} | |
| self.active = false | |
| return self | |
| end | |
| --[[ | |
| This gets our projected coordinate on the iPad screen. | |
| --]] | |
| function Vertex:Project() | |
| local c | |
| c = self.shape.space:Project(self.coordinate) | |
| if c[4] then | |
| self.projection = c[1] | |
| self.rScale = c[2] | |
| self.level = c[3] | |
| else | |
| self.projection = nil | |
| self.level = 0 | |
| end | |
| if self.normal then | |
| self.pnormal = self.shape.space:ProjectDirection(self.normal) | |
| end | |
| end | |
| --[[ | |
| Vertices know which edges end at themselves. | |
| --]] | |
| function Vertex:addEdge(e) | |
| table.insert(self.edges,e) | |
| if self.valency then | |
| self.valency = self.valency - 1 | |
| end | |
| end | |
| --[[ | |
| This is our draw routine. We draw an ellipse, suitably scaled, with a | |
| halo of the background colour as a "shadow". | |
| --]] | |
| function Vertex:draw() | |
| local r,c | |
| if self.projection then | |
| if self.radius then | |
| r = self.radius | |
| else | |
| r = self.shape.defaults.vertex.radius | |
| end | |
| self.pRadius = r * self.rScale * self.shape.space.intScale * self.shape.space.extScale | |
| if self.colour then | |
| c = self.colour | |
| else | |
| c = self.shape.defaults.vertex.colour | |
| end | |
| if self.active then | |
| c = Colour.tint(c,50) | |
| end | |
| stroke(self.shape.space.bgColour) | |
| strokeWidth(2) -- make configurable | |
| fill(c) | |
| ellipse(self.projection.x, self.projection.y, self.pRadius ) | |
| end | |
| end | |
| --[[ | |
| See if it was us that was touched. | |
| --]] | |
| function Vertex:isTouchedBy(touch) | |
| local v = vec2(touch.x - WIDTH/2,touch.y - HEIGHT/2) - self.projection | |
| local r = self.pRadius or 20 | |
| if v:len() < r then | |
| return true | |
| else | |
| return false | |
| end | |
| end | |
| --[[ | |
| If we were touched then if it was a tap we toggle our active status, | |
| otherwise we move in a plane parallel to the current iPad screen. | |
| --]] | |
| function Vertex:processTouches(g) | |
| if g.num == 1 then | |
| if g.type.tap | |
| and self.shape.touched == self | |
| then | |
| if g.type.finished then | |
| self.active = not self.active | |
| end | |
| elseif self.active | |
| or self.shape.touched == self | |
| then | |
| self:move(g.touchesArr[1]) | |
| end | |
| elseif g.num == 2 then | |
| if g.type.tap | |
| and g.type.ended | |
| and g.updated | |
| and self.shape.touched == self | |
| then | |
| self.shape:toggleVertices(self) | |
| end | |
| end | |
| end | |
| --[[ | |
| This is the function that moves us. | |
| --]] | |
| function Vertex:move(t) | |
| local v,q | |
| if t.updated then | |
| v = self.projection + vec2(t.touch.deltaX,t.touch.deltaY) | |
| self.coordinate = self.shape.space:invProject(v,self.coordinate) | |
| for k,e in pairs(self.edges) do | |
| e:Shorten() | |
| end | |
| end | |
| end | |
| --[[ | |
| Delete an edge. | |
| --]] | |
| function Vertex:deleteEdge(e) | |
| for k,v in pairs(self.edges) do | |
| if v == e then | |
| self.edges[k] = nil | |
| end | |
| end | |
| end | |
| --[[ | |
| Convert our data to a string for serialisation. | |
| --]] | |
| function Vertex:toString() | |
| local s | |
| s = self.coordinate:tostring() | |
| s = s .. "," | |
| if self.radius then | |
| s = s .. self.radius | |
| end | |
| s = s .. "," | |
| if self.colour then | |
| s = s .. tostring(self.colour) | |
| end | |
| return s | |
| end | |
| --[[ | |
| (Technically not a class function) define a new vertex from serialised | |
| data. | |
| --]] | |
| function Vertex.fromString(s) | |
| local i,j,a,x,y,z | |
| i,j = string.find(s,")") | |
| a = string.sub(s,2,i-1) | |
| s = string.sub(s,i+2,-1) | |
| i,j = string.find(a,",") | |
| x = string.sub(a,1,i-1) | |
| a = string.sub(a,i + 1,-1) | |
| i,j = string.find(a,",") | |
| y = string.sub(a,1,i-1) | |
| z = string.sub(a,i + 1,-1) | |
| x = tonumber(x) | |
| y = tonumber(y) | |
| z = tonumber(z) | |
| v = Vertex(x,y,z) | |
| i,j = string.find(s,",") | |
| a = string.sub(s,1,i-1) | |
| s = string.sub(s,i + 2,-2) | |
| if a ~= "" then | |
| a = tonumber(a) | |
| v.radius = a | |
| end | |
| if s ~= "" then | |
| i,j = string.find(s,",") | |
| x = string.sub(s,1,i-1) | |
| s = string.sub(s,i + 1,-1) | |
| i,j = string.find(s,",") | |
| y = string.sub(s,1,i-1) | |
| s = string.sub(s,i + 1,-1) | |
| i,j = string.find(s,",") | |
| z = string.sub(s,1,i-1) | |
| a = string.sub(s,i + 1,-1) | |
| x = tonumber(x) | |
| y = tonumber(y) | |
| z = tonumber(z) | |
| a = tonumber(a) | |
| v.colour = color(x,y,z,a) | |
| end | |
| return v | |
| end | |
| --[[ | |
| The Edge class represents an edge between two vertices. We get two | |
| vertices, and optionally a thickness and a colour. | |
| --]] | |
| local Edge = class() | |
| function Edge:init(u,v,...) | |
| -- vertices at end points (Data3dVertex objects) | |
| -- colour | |
| -- thickness | |
| -- projected coordinates | |
| -- distance from eye | |
| self.head = u | |
| self.tail = v | |
| u:addEdge(self) | |
| v:addEdge(self) | |
| for k,v in ipairs(arg) do | |
| if type(v) == "number" then | |
| self.thickness = v | |
| elseif type(v) == "table" then | |
| if v:is_a(Shape) then | |
| self.shape = v | |
| else | |
| print("Expecting a colour") | |
| return false | |
| end | |
| elseif type(v) == "userdata" then | |
| if v.r then | |
| self.colour = v | |
| else | |
| print("Expecting a colour") | |
| return false | |
| end | |
| end | |
| end | |
| return self | |
| end | |
| --[[ | |
| To simulate the 3d nature of the vertices, the edges "stop" at the | |
| apparent edge of the vertex sphere. This is done by shortening the | |
| edges by the appropriate amount. | |
| --]] | |
| function Edge:Shorten() | |
| local u,v,w | |
| u = self.head.coordinate | |
| v = self.tail.coordinate | |
| w = u - v | |
| w = w:normalise() | |
| if self.head.radius then | |
| u = u - self.head.radius * w | |
| else | |
| u = u - self.shape.defaults.vertex.radius * w | |
| end | |
| if self.tail.radius then | |
| v = v + self.tail.radius * w | |
| else | |
| v = v + self.shape.defaults.vertex.radius * w | |
| end | |
| self.headpt = u | |
| self.tailpt = v | |
| end | |
| --[[ | |
| This projects us onto the iPad screen. | |
| --]] | |
| function Edge:Project() | |
| local u,w,uu,ww | |
| if not self.headpt then | |
| self:Shorten() | |
| end | |
| u = self.shape.space:Project(self.headpt) | |
| w = self.shape.space:Project(self.tailpt) | |
| if u[4] or w[4] then | |
| self.level = math.min(u[3],w[3]) | |
| self.mlevel = math.max(u[3],w[3]) | |
| uu = u[1] | |
| ww = w[1] | |
| if not u[4] then | |
| uu = ww - uu | |
| uu = uu:normalize() | |
| uu = self.shape.space.boundary * uu | |
| uu = ww + uu | |
| end | |
| if not w[4] then | |
| ww = uu - ww | |
| ww = ww:normalize() | |
| ww = self.shape.space.boundary * ww | |
| ww = ww + uu | |
| end | |
| self.projHead = uu | |
| self.projTail = ww | |
| end | |
| end | |
| --[[ | |
| Given one vertex, return the one at the other end. | |
| --]] | |
| function Edge:getVertex(v) | |
| if self.head == v then | |
| return self.tail | |
| elseif self.tail == v then | |
| return self.head | |
| else | |
| return false | |
| end | |
| end | |
| --[[ | |
| Do our best to get rid of ourselves. | |
| --]] | |
| function Edge:destroy() | |
| self.head:deleteEdge(self) | |
| self.tail:deleteEdge(self) | |
| end | |
| --[[ | |
| This is the draw function. | |
| --]] | |
| function Edge:draw() | |
| local c,t,b | |
| if self.colour then | |
| c = self.colour | |
| else | |
| c = self.shape.defaults.edge.colour | |
| end | |
| if self.head.active and self.tail.active then | |
| c = Colour.tint(c,50) | |
| end | |
| if self.thickness then | |
| t = self.thickness | |
| else | |
| t = self.shape.defaults.edge.thickness | |
| end | |
| b = t + 2 | |
| stroke(self.shape.space.bgColour) | |
| strokeWidth(b) | |
| line(self.projHead.x, self.projHead.y, self.projTail.x, self.projTail.y) | |
| stroke(c) | |
| strokeWidth(t) | |
| line(self.projHead.x, self.projHead.y, self.projTail.x, self.projTail.y) | |
| end | |
| --[[ | |
| This function computes whether or not we are over some other edge or vertex. | |
| --]] | |
| function Edge:isOver(e) | |
| if e:is_a(Edge) then | |
| return Vec3.isOverLine( | |
| self.shape.space:applyIntTransformation(self.headpt), | |
| self.shape.space:applyIntTransformation(self.tailpt), | |
| self.shape.space:applyIntTransformation(e.headpt), | |
| self.shape.space:applyIntTransformation(e.tailpt), | |
| self.shape.space.eye) | |
| else | |
| local r | |
| if self.thickness then | |
| r = self.thickness | |
| else | |
| r = self.shape.defaults.edge.thickness | |
| end | |
| r = r/(self.shape.space.intScale * self.shape.space.extScale) | |
| if e.radius then | |
| r = r + e.radius | |
| else | |
| r = r + self.shape.defaults.vertex.radius | |
| end | |
| return Vec3.isOverPoint( | |
| self.shape.space:applyIntTransformation(self.head.coordinate), | |
| self.shape.space:applyIntTransformation(self.tail.coordinate), | |
| self.shape.space:applyIntTransformation(e.coordinate), | |
| r, | |
| self.shape.space.eye) | |
| end | |
| end | |
| --[[ | |
| Assuming that we are over an edge or vertex, this returns the | |
| intersection point (on the iPad screen) for clipping. | |
| --]] | |
| function Edge:Intersect(v) | |
| if v:is_a(Edge) then | |
| local a,b,c,d | |
| a = self.projHead - self.projTail | |
| b = v.projHead - v.projTail | |
| c = a:cross(b) | |
| return self.projHead:cross(self.projTail)/c*b - v.projHead:cross(v.projTail)/c*a | |
| else | |
| return v.projection | |
| end | |
| end | |
| --[[ | |
| This serialises our properties. | |
| --]] | |
| function Edge:toString() | |
| local s = "" | |
| if self.thickness then | |
| s = s .. self.thickness | |
| end | |
| s = s .. "," | |
| if self.colour then | |
| s = s .. tostring(self.colour) | |
| end | |
| return s | |
| end | |
| --[[ | |
| This sets our properties from a string. | |
| --]] | |
| function Edge:setProperties(s) | |
| local i,j,r | |
| if s and s ~= "," and s ~= "" then | |
| i,j = string.find(s,",") | |
| r = string.sub(s,1,i-1) | |
| s = string.sub(s,i + 2,-2) | |
| if r ~= "" then | |
| self.thickness = r | |
| end | |
| i,j = string.find(s,",") | |
| x = string.sub(s,1,i-1) | |
| s = string.sub(s,i + 1,-1) | |
| i,j = string.find(s,",") | |
| y = string.sub(s,1,i-1) | |
| s = string.sub(s,i + 1,-1) | |
| i,j = string.find(s,",") | |
| z = string.sub(s,1,i-1) | |
| a = string.sub(s,i + 1,-1) | |
| x = tonumber(x) | |
| y = tonumber(y) | |
| z = tonumber(z) | |
| a = tonumber(a) | |
| self.colour = color(x,y,z,a) | |
| end | |
| end | |
| local Triangle = class() | |
| function Triangle:init(t,col,imgc,f) | |
| self.vertices = t | |
| local a = self.vertices[1].coordinate | |
| local b = self.vertices[2].coordinate | |
| local c = self.vertices[3].coordinate | |
| b = b - a | |
| c = c - a | |
| b = b:cross(c) | |
| if b:is_zero() then | |
| self.normal = Vec3(0,0,0) | |
| else | |
| self.normal = b:normalise() | |
| end | |
| self.colour = col | |
| self.imgcoords = {} | |
| imgc = imgc or {} | |
| for i = 1,3 do | |
| self.imgcoords[i] = imgc[i] or vec2(0,0) | |
| end | |
| self.face = f | |
| end | |
| function Triangle:getProjectedVertices() | |
| return {self.vertices[1].projection, | |
| self.vertices[2].projection, | |
| self.vertices[3].projection} | |
| end | |
| function Triangle:getVertices() | |
| return {self.vertices[1], | |
| self.vertices[2], | |
| self.vertices[3]} | |
| end | |
| function Triangle:getTexCoords() | |
| return {self.imgcoords[1], | |
| self.imgcoords[2], | |
| self.imgcoords[3]} | |
| end | |
| function Triangle:Project() | |
| self.level = math.min(self.vertices[1].level, | |
| self.vertices[2].level, | |
| self.vertices[3].level) | |
| end | |
| local Face = class() | |
| function Face:init(t,c,imgcoords) | |
| self.triangles = {} | |
| self.vertices = t | |
| self.colour = c | |
| if imgcoords then | |
| self.imgcoords = imgcoords | |
| end | |
| self:triangulate() | |
| end | |
| function Face:triangulate() | |
| -- assume for now that our face is convex and planar | |
| local v = {} | |
| local n = 0 | |
| local t = {} | |
| for k,u in ipairs(self.vertices) do | |
| table.insert(v,u) | |
| n = n + 1 | |
| end | |
| local c = {} | |
| if self.imgcoords then | |
| for k,u in ipairs(self.imgcoords) do | |
| table.insert(c,u) | |
| end | |
| end | |
| local a,b,d | |
| for i = 1,n - 2 do | |
| if i%2 == 1 then | |
| a = table.remove(v) | |
| else | |
| a = table.remove(v,1) | |
| end | |
| n = n - 1 | |
| if self.imgcoords then | |
| if i%2 == 1 then | |
| b = table.remove(c) | |
| else | |
| b = table.remove(c,1) | |
| end | |
| d = {b,c[1],c[n]} | |
| else | |
| d = nil | |
| end | |
| table.insert(t,Triangle({a,v[1],v[n]},self.colour,d,self)) | |
| end | |
| self.triangles = t | |
| end | |
| function Face:Project() | |
| local l = 0 | |
| local n = 0 | |
| for k,v in ipairs(self.vertices) do | |
| l = l + v.level | |
| n = n + 1 | |
| end | |
| l = l/n | |
| for k,v in ipairs(self.triangles) do | |
| v.level = l | |
| end | |
| end | |
| return {Vertex, Edge, Face, Triangle} | |
| --]==] |
This file contains hidden or 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
| --[==[ | |
| -- Predefined Shapes | |
| --[[ | |
| Define some common shapes. | |
| --]] | |
| local Shape = cimport "Shape" | |
| local Vertex, Edge, Face, Triangle = unpack(cimport "Shapes",nil) | |
| local Vec3 = cimport "Vec3" | |
| local preDefinedShapes = { | |
| "Cube", | |
| "Chair", | |
| "Borromean", | |
| "Sticks", | |
| "TwoSticks", | |
| "Buckyball", | |
| "Octohedron", | |
| "CubeFaceted", | |
| "Torus", | |
| "Sphere", | |
| "Die", | |
| "Globe" | |
| } | |
| --[[ | |
| Cube. | |
| --]] | |
| function Shape.Cube(s) | |
| for n = 0,7 do | |
| i = 2*(n%2) - 1 | |
| j = 2*(math.floor(n/2)%2) -1 | |
| k = 2*(math.floor(n/4)%2) -1 | |
| s:addVertex(Vertex(i,j,k)) | |
| end | |
| s:setVertexDefaults(.2,color(255,0,0,255),3) | |
| s:setEdgeDefaults(10,color(255,255,0,255)) | |
| s:addEdgesByValency() | |
| end | |
| --[[ | |
| Chair: this is one form of cyclic hexane. | |
| --]] | |
| function Shape.Chair(s) | |
| s:addVertex(Vertex( -0.0435, -1.4573, -0.2258)) | |
| s:addVertex(Vertex( 1.2404, -0.7663, 0.2258)) | |
| s:addVertex(Vertex( -1.2838, -0.6910, 0.2258)) | |
| s:addVertex(Vertex( 1.2838, 0.6911, -0.2257)) | |
| s:addVertex(Vertex( -1.2404, 0.7662, -0.2258)) | |
| s:addVertex(Vertex( 0.0435, 1.4573, 0.2258)) | |
| s:setVertexDefaults(.2,color(255,0,0,255),2) | |
| s:setEdgeDefaults(10,color(255,255,0,255)) | |
| s:addEdgesByValency() | |
| end | |
| --[[ | |
| Rectangular version of the borromean rings. | |
| --]] | |
| function Shape.Borromean(s) | |
| local edges = {} | |
| local e = {} | |
| local r = Colour.svg.Red | |
| local b = Colour.svg.Blue | |
| local g = Colour.svg.Green | |
| s:addVertex(Vertex(2,1,0,r)) | |
| s:addVertex(Vertex(2,-1,0,r)) | |
| s:addVertex(Vertex(-2,-1,0,r)) | |
| s:addVertex(Vertex(-2,1,0,r)) | |
| e[1] = {{0,1,{r}},{1,2,{r}},{2,3,{r}},{3,0,{r}}} | |
| s:addVertex(Vertex(0,2,1,b)) | |
| s:addVertex(Vertex(0,2,-1,b)) | |
| s:addVertex(Vertex(0,-2,-1,b)) | |
| s:addVertex(Vertex(0,-2,1,b)) | |
| e[2] = {{4,5,{b}},{5,6,{b}},{6,7,{b}},{7,4,{b}}} | |
| s:addVertex(Vertex(1,0,2,g)) | |
| s:addVertex(Vertex(-1,0,2,g)) | |
| s:addVertex(Vertex(-1,0,-2,g)) | |
| s:addVertex(Vertex(1,0,-2,g)) | |
| e[3] = {{8,9,{g}},{9,10,{g}},{10,11,{g}},{11,8,{g}}} | |
| for k,v in pairs(e) do | |
| for j,u in pairs(v) do | |
| table.insert(edges,u) | |
| end | |
| end | |
| s:setEdgesBetween(edges) | |
| s:setVertexDefaults(.1,color(255,0,0,255)) | |
| s:setEdgeDefaults(10,color(255,255,0,255)) | |
| s:GenerateEdges() | |
| s.checkIntersections = true | |
| end | |
| --[[ | |
| Four overlapping sticks, showing that "is over" is not a transitive | |
| relation. | |
| --]] | |
| function Shape.Sticks(s) | |
| local ver = { | |
| Vertex(1,2,1), | |
| Vertex(1,-2,-1), | |
| Vertex(-1,-2,1), | |
| Vertex(-1,2,-1), | |
| Vertex(2,1,-1), | |
| Vertex(-2,1,1), | |
| Vertex(-2,-1,-1), | |
| Vertex(2,-1,1) | |
| } | |
| for k,v in ipairs(ver) do | |
| s:addVertex(v) | |
| end | |
| local ed = {{0,1},{2,3},{4,5},{6,7}} | |
| for k,v in ipairs(ed) do | |
| s:addEdge(Edge(ver[v[1] + 1], ver[v[2] + 1])) | |
| end | |
| s:setVertexDefaults(.2,color(255,0,0,255)) | |
| s:setEdgeDefaults(10,color(255,255,0,255)) | |
| s:edgesRecalc() | |
| s.checkIntersections = true | |
| end | |
| --[[ | |
| Two sticks: used for debugging the four sticks. | |
| --]] | |
| function Shape.TwoSticks(s) | |
| local ver = { | |
| Vertex(1,2,1), | |
| Vertex(1,-2,-1), | |
| Vertex(2,1,-1), | |
| Vertex(-2,1,1) | |
| } | |
| for k,v in ipairs(ver) do | |
| s:addVertex(v) | |
| end | |
| local ed = {{0,1},{2,3}} | |
| for k,v in ipairs(ed) do | |
| s:addEdge(Edge(ver[v[1] + 1], ver[v[2] + 1])) | |
| end | |
| s:setVertexDefaults(.2,color(255,0,0,255)) | |
| s:setEdgeDefaults(10,color(255,255,0,255)) | |
| s:edgesRecalc() | |
| s.checkIntersections = true | |
| end | |
| --[[ | |
| Buckminster fullerene (aka a football). | |
| --]] | |
| function Shape.Buckyball(s) | |
| local phi, qa,v | |
| phi = (math.sqrt(5) + 1)/2 | |
| for l = 0,2 do | |
| qa= Quaternion.Rotation(2*l*math.pi/3,1,1,1) | |
| for n = 0,7 do | |
| i = 2*(n%2) - 1 | |
| j = 2*(math.floor(n/2)%2) -1 | |
| k = 2*(math.floor(n/4)%2) -1 | |
| v = Vec3(i*2,j*(1+2*phi),k*phi) | |
| v = v^qa | |
| s:addVertex(Vertex(v)) | |
| v = Vec3(i,j*(2+phi),k*2*phi) | |
| v = v^qa | |
| s:addVertex(Vertex(v)) | |
| end | |
| for n = 0,3 do | |
| i = 2*(n%2) - 1 | |
| j = 2*(math.floor(n/2)%2) -1 | |
| v = Vec3(0,i,j*3*phi) | |
| v = v^qa | |
| s:addVertex(Vertex(v)) | |
| end | |
| end | |
| s:setVertexDefaults(.1,color(255,0,0,255),3) | |
| s:setEdgeDefaults(5,color(255,255,0,255)) | |
| s:addEdgesByValency() | |
| end | |
| --[[ | |
| An octohedron. | |
| --]] | |
| function Shape.Octohedron(s) | |
| local q, v | |
| for j = 0,2 do | |
| q = Quaternion.Rotation(2*j*math.pi/3,1,1,1) | |
| for i = -1,1,2 do | |
| v = i*2*Vec3.e1^q | |
| s:addVertex(Vertex(v)) | |
| end | |
| end | |
| s:setVertexDefaults(.2,color(255,0,0,255),4) | |
| s:setEdgeDefaults(10,color(255,255,0,255)) | |
| s:addEdgesByValency() | |
| end | |
| --[[ | |
| A Lattice. | |
| --]] | |
| function Shape.Lattice(s) | |
| local q, v, n | |
| local sep = 2 | |
| local m = 2 | |
| for k = -m,m do | |
| for j = -m,m do | |
| for i = -m,m do | |
| n = 6 | |
| if math.abs(k) == m then | |
| n = n - 1 | |
| end | |
| if math.abs(j) == m then | |
| n = n - 1 | |
| end | |
| if math.abs(i) == m then | |
| n = n - 1 | |
| end | |
| v = Vertex(sep * Vec3(i,j,k)) | |
| v.valency = n | |
| s:addVertex(v) | |
| end | |
| end | |
| end | |
| s:setVertexDefaults(.1,color(255,0,0,255)) | |
| s:setEdgeDefaults(10,color(255,255,0,255)) | |
| s:addEdgesByValency() | |
| end | |
| --[[ | |
| Cube with faces. | |
| --]] | |
| function Shape.CubeFaceted(s) | |
| local v = {} | |
| for n = 0,7 do | |
| i = 2*(n%2) - 1 | |
| j = 2*(math.floor(n/2)%2) -1 | |
| k = 2*(math.floor(n/4)%2) -1 | |
| table.insert(v,Vertex(i,j,k)) | |
| end | |
| s.surface = true | |
| s:addFace(Face({v[1],v[2],v[6],v[5]},Colour.svg.Red)) | |
| s:addFace(Face({v[3],v[4],v[2],v[1]},Colour.svg.Blue)) | |
| s:addFace(Face({v[5],v[6],v[8],v[7]},Colour.svg.Green)) | |
| s:addFace(Face({v[7],v[8],v[4],v[3]},Colour.svg.Yellow)) | |
| s:addFace(Face({v[5],v[7],v[3],v[1]},Colour.svg.Orange)) | |
| s:addFace(Face({v[2],v[4],v[8],v[6]},Colour.svg.Purple)) | |
| end | |
| --[[ | |
| Cube with pictures. | |
| --]] | |
| function Shape.Die(s) | |
| local v = {} | |
| for n = 0,7 do | |
| i = 2*(n%2) - 1 | |
| j = 2*(math.floor(n/2)%2) -1 | |
| k = 2*(math.floor(n/4)%2) -1 | |
| table.insert(v,Vertex(i,j,k)) | |
| end | |
| s.surface = true | |
| if not s:setTexture("Die") then | |
| pushStyle() | |
| local r = 100 | |
| local rl = 26 | |
| local rr = r - rl | |
| local rd = r/10 | |
| local die = image(r,8*r) | |
| setContext(die) | |
| fill(255, 255, 255, 255) | |
| strokeWidth(0) | |
| noSmooth() | |
| rect(0,0,r,6*r) | |
| smooth() | |
| noFill() | |
| strokeWidth(3) | |
| stroke(0, 56, 255, 255) | |
| rectMode(CORNERS) | |
| for i = 1,6 do | |
| rect(0,(i-1)*r,r,i*r) | |
| end | |
| strokeWidth(0) | |
| fill(0, 0, 0, 255) | |
| ellipseMode(RADIUS) | |
| for i = 1,3 do | |
| ellipse(r/2, i*2*r- 3*r/2,rd) | |
| ellipse(rr, i*r + 2*r + rr,rd) | |
| ellipse(rl, i*r + 2*r + rl,rd) | |
| end | |
| for i = 1,5 do | |
| ellipse(rl, i*r + rr,rd) | |
| ellipse(rr, i*r + rl,rd) | |
| end | |
| ellipse(rl,11*r/2,rd) | |
| ellipse(rr,11*r/2,rd) | |
| setContext() | |
| popStyle() | |
| s:setTexture(die,"Die") | |
| end | |
| s:addFace(Face( | |
| {v[1],v[2],v[6],v[5]}, | |
| Colour.svg.White, | |
| {vec2(0,0),vec2(0,.125),vec2(1,.125),vec2(1,0)} | |
| )) | |
| s:addFace(Face( | |
| {v[3],v[4],v[2],v[1]}, | |
| Colour.svg.White, | |
| {vec2(0,.125),vec2(0,.25),vec2(1,.25),vec2(1,.125)} | |
| )) | |
| s:addFace(Face( | |
| {v[5],v[6],v[8],v[7]}, | |
| Colour.svg.White, | |
| {vec2(0,.25),vec2(0,.375),vec2(1,.375),vec2(1,.25)} | |
| )) | |
| s:addFace(Face( | |
| {v[7],v[8],v[4],v[3]}, | |
| Colour.svg.White, | |
| {vec2(0,.375),vec2(0,.5),vec2(1,.5),vec2(1,.375)} | |
| )) | |
| s:addFace(Face( | |
| {v[5],v[7],v[3],v[1]}, | |
| Colour.svg.White, | |
| {vec2(0,.5),vec2(0,.625),vec2(1,.625),vec2(1,.5)} | |
| )) | |
| s:addFace(Face( | |
| {v[2],v[4],v[8],v[6]}, | |
| Colour.svg.White, | |
| {vec2(0,.625),vec2(0,.75),vec2(1,.75),vec2(1,.625)} | |
| )) | |
| end | |
| function Shape.Torus(s,t) | |
| t = t or {} | |
| local innerR = t.innerRadius or 1 | |
| local outerR = t.outerRadius or 2 | |
| local innerA = t.innerAxis or Vec3.e3 | |
| local outerA = t.outerAxis or Vec3.e2 | |
| local nipts,nopts | |
| if t.innerPoints then | |
| nipts = t.innerPoints | |
| elseif t.maxInnerRectangle then | |
| nipts = math.ceil(2*math.pi * innerR / t.maxInnerRectangle) | |
| elseif t.points then | |
| nipts = t.points | |
| elseif t.maxRectangle then | |
| nipts = math.ceil(2*math.pi * innerR/t.maxRectangle) | |
| else | |
| nipts = 10 | |
| end | |
| if t.outerPoints then | |
| nopts = t.outerPoints | |
| elseif t.maxOuterRectangle then | |
| nopts = math.ceil(2*math.pi * (innerR + outerR) / t.maxOuterRectangle) | |
| elseif t.points then | |
| nopts = t.points | |
| elseif t.maxRectangle then | |
| nopts = math.ceil(2*math.pi * (innerR + outerR) / t.maxRectangle) | |
| else | |
| nopts = 20 | |
| end | |
| local faces = {} | |
| local e1 = Vec3.e1 | |
| local e2 = Vec3.e2 | |
| local ipts,qi,qj,w,n | |
| local pts = {} | |
| local istep = 360/nipts | |
| local ostep = 360/nopts | |
| local iang = 180/istep | |
| local oang = 180/ostep | |
| for i = 1,nopts do | |
| qi = Quaternion.Rotation(math.rad(i*ostep+oang),outerA) | |
| ipts = {} | |
| for j = 1,nipts do | |
| qj = Quaternion.Rotation(math.rad(j*istep+iang),innerA) | |
| n = e2^(qi * qj) | |
| w = Vertex(outerR * e1^qi + innerR * n) | |
| w.normal = n | |
| table.insert(ipts,w) | |
| table.insert(faces,{ | |
| {i,j}, | |
| {i%nopts+1,j}, | |
| {i%nopts+1,j%nipts+1}, | |
| {i,j%nipts + 1} | |
| }) | |
| end | |
| table.insert(pts,ipts) | |
| end | |
| for k,v in ipairs(faces) do | |
| s:addFace(Face({pts[v[1][1]][v[1][2]], | |
| pts[v[2][1]][v[2][2]], | |
| pts[v[3][1]][v[3][2]], | |
| pts[v[4][1]][v[4][2]]})) | |
| end | |
| s.surface = true | |
| end | |
| function Shape.Sphere(s,t) | |
| t = t or {} | |
| local radius = t.radius or 1.5 | |
| local npts | |
| if t.points then | |
| npts = t.points | |
| elseif t.maxRectangle then | |
| npts = math.ceil(2*math.pi * radius/t.maxRectangle) | |
| else | |
| npts = 10 | |
| end | |
| local nipts = npts | |
| local nopts = 2*npts | |
| local faces = {} | |
| local e1 = Vec3.e1 | |
| local ipts,qi,qj,w,n | |
| local pts = {} | |
| local istep = 180/nipts | |
| local ostep = 360/nopts | |
| for i = 1,nopts do | |
| qi = Quaternion.Rotation(math.rad(i*ostep),Vec3.e2) | |
| ipts = {} | |
| for j = 1,nipts-1 do | |
| qj = Quaternion.Rotation(math.rad(j*istep-90),Vec3.e3) | |
| n = e1^(qi * qj) | |
| w = Vertex(radius * n) | |
| w.normal = n | |
| table.insert(ipts,w) | |
| if j ~= nipts-1 then | |
| table.insert(faces,{ | |
| {i,j}, | |
| {i%nopts+1,j}, | |
| {i%nopts+1,j+1}, | |
| {i,j + 1} | |
| }) | |
| end | |
| end | |
| table.insert(pts,ipts) | |
| end | |
| for k,v in ipairs(faces) do | |
| s:addFace(Face({pts[v[1][1]][v[1][2]], | |
| pts[v[2][1]][v[2][2]], | |
| pts[v[3][1]][v[3][2]], | |
| pts[v[4][1]][v[4][2]]})) | |
| end | |
| local top = Vertex(radius * Vec3.e2) | |
| top.normal = Vec3.e2 | |
| local bot = Vertex(-radius * Vec3.e2) | |
| bot.normal = -Vec3.e2 | |
| for i = 1,nopts do | |
| s:addFace(Face({pts[i][1],bot,pts[i%nopts + 1][1]})) | |
| s:addFace(Face({pts[i][nipts-1],pts[i%nopts + 1][nipts-1],top})) | |
| end | |
| s.surface = true | |
| s.faceted = true | |
| end | |
| function Shape.Globe(s,t) | |
| t = t or {} | |
| local radius = t.radius or 1.5 | |
| local npts | |
| if t.points then | |
| npts = t.points | |
| elseif t.maxRectangle then | |
| npts = math.ceil(2*math.pi * radius/t.maxRectangle) | |
| else | |
| npts = 10 | |
| end | |
| local nipts = npts | |
| local nopts = 2*npts | |
| local faces = {} | |
| local e1 = Vec3.e1 | |
| local ipts,qi,qj,w,n | |
| local pts = {} | |
| local istep = 180/nipts | |
| local ostep = 360/nopts | |
| for i = 1,nopts do | |
| qi = Quaternion.Rotation(math.rad(i*ostep),Vec3.e2) | |
| ipts = {} | |
| for j = 1,nipts-1 do | |
| qj = Quaternion.Rotation(math.rad(j*istep-90),Vec3.e3) | |
| n = e1^(qi * qj) | |
| w = Vertex(radius * n) | |
| w.normal = n | |
| table.insert(ipts,w) | |
| --[[ | |
| if j ~= nipts-1 then | |
| table.insert(faces,{ | |
| {i,j}, | |
| {i%nopts+1,j}, | |
| {i%nopts+1,j+1}, | |
| {i,j + 1} | |
| }) | |
| end | |
| --]] | |
| end | |
| table.insert(pts,ipts) | |
| end | |
| for i = 1,nopts do | |
| for j = 1,nipts-2 do | |
| s:addFace(Face({pts[i][j], | |
| pts[i%nopts+1][j], | |
| pts[i%nopts+1][j+1], | |
| pts[i][j+1]}, | |
| Colour.svg.White, | |
| { | |
| vec2((i-1)/nopts,j/nipts), | |
| vec2(i/nopts,j/nipts), | |
| vec2(i/nopts,(j+1)/nipts), | |
| vec2((i-1)/nopts,(j+1)/nipts) | |
| })) | |
| end | |
| end | |
| --[[ | |
| --]] | |
| local top = Vertex(radius * Vec3.e2) | |
| top.normal = Vec3.e2 | |
| local bot = Vertex(-radius * Vec3.e2) | |
| bot.normal = -Vec3.e2 | |
| for i = 1,nopts do | |
| s:addFace(Face({ | |
| pts[i][1],bot,pts[i%nopts + 1][1] | |
| }, | |
| Colour.svg.White, | |
| { | |
| vec2((i-1)/nopts,1/nipts), | |
| vec2(i/nopts,0), | |
| vec2(i/nopts,1/nipts) | |
| } | |
| )) | |
| s:addFace(Face({ | |
| pts[i][nipts-1],pts[i%nopts + 1][nipts-1],top | |
| }, | |
| Colour.svg.White, | |
| { | |
| vec2((i-1)/nopts,1-1/nipts), | |
| vec2(i/nopts,1-1/nipts), | |
| vec2((i-.5)/nopts,1) | |
| } | |
| )) | |
| end | |
| s.surface = true | |
| --s.faceted = true | |
| if not s:setTextureByName("Globe") then | |
| s:setTexture(globe,"Globe") | |
| end | |
| end | |
| local function buildAffine(f) | |
| local o = f(Vec3.origin) | |
| local e1 = f(Vec3.e1) - o | |
| local e2 = f(Vec3.e2) - o | |
| local e3 = f(Vec3.e3) - o | |
| return function(u) | |
| return Vec3( | |
| e1.x * u.x + e2.x * u.y + e3.x * u.z + o.x, | |
| e1.y * u.x + e2.y * u.y + e3.y * u.z + o.y, | |
| e1.z * u.x + e2.z * u.y + e3.z * u.z + o.z | |
| ) | |
| end | |
| end | |
| local function buildLinear(f) | |
| local e1 = f(Vec3.e1) | |
| local e2 = f(Vec3.e2) | |
| local e3 = f(Vec3.e3) | |
| return function(u) | |
| return Vec3( | |
| e1.x * u.x + e2.x * u.y + e3.x * u.z, | |
| e1.y * u.x + e2.y * u.y + e3.y * u.z, | |
| e1.z * u.x + e2.z * u.y + e3.z * u.z | |
| ) | |
| end | |
| end | |
| --]==] |
This file contains hidden or 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
| --[==[ | |
| --[[ | |
| Track definitions. | |
| --]] | |
| function TrackPoints(a) | |
| a = a or {} | |
| local pts = a.points or {} | |
| local t = a.start or 0 | |
| local r = a.step or .1 | |
| r = r*r | |
| local s = a.delta or .1 | |
| local f = a.pathFunction or function(q) return q*vec3(1,0,0) end | |
| local nf = a.normalFunction or function(q) return vec3(0,1,0) end | |
| local b = a.finish or 1 | |
| local tpt = f(t) | |
| table.insert(pts,{tpt, | |
| tangent({delta = s, pathFunction = f, time = t}), | |
| nf(t),t}) | |
| local dis | |
| local p | |
| while t < b do | |
| dis = 0 | |
| while dis < r do | |
| t = t + s | |
| p = f(t) | |
| dis = dis + p:distSqr(tpt) | |
| tpt = p | |
| end | |
| if t > b then | |
| t = b | |
| p = f(b) | |
| end | |
| table.insert(pts,{p, | |
| tangent({delta = s, pathFunction = f, time = t}), | |
| nf(t),t}) | |
| tpt = p | |
| end | |
| return pts | |
| end | |
| function tangent(a) | |
| local s = a.delta/2 or .1 | |
| local f = a.pathFunction or function(q) return q*vec3(1,0,0) end | |
| local t = a.time or 0 | |
| local u = f(t-s) | |
| local v = f(t+s) | |
| return (v-u)/(2*s) | |
| end | |
| local Tracks = {} | |
| function Tracks.torus(p,q) | |
| local innerRa = 10 | |
| local innerRb = 10 | |
| local outerR = 30 | |
| local trackFunction = function(t) | |
| local it = p*t*2*math.pi | |
| local ot = q*t*2*math.pi | |
| return vec3( | |
| (outerR + innerRb*math.cos(it))*math.cos(ot), | |
| innerRa*math.sin(it), | |
| (outerR + innerRb*math.cos(it))*math.sin(ot) | |
| ) | |
| end | |
| local trackNormal = function(t) | |
| local it = p*t*2*math.pi | |
| local ot = q*t*2*math.pi | |
| return vec3( | |
| innerRa*math.cos(it)*math.cos(ot), | |
| innerRb*math.sin(it), | |
| innerRa*math.cos(it)*math.sin(ot) | |
| ) | |
| end | |
| local coreFunction = function(t) | |
| local ot = q*t*2*math.pi | |
| return vec3( | |
| outerR*math.cos(ot), | |
| 0, | |
| outerR*math.sin(ot) | |
| ) | |
| end | |
| local maxHeight = innerRa | |
| local minHeight = -innerRa | |
| return trackFunction, trackNormal, maxHeight, minHeight | |
| end | |
| function Tracks.mobius() | |
| local r = 30 | |
| local trackFunction = function(t) | |
| local a = 2*math.pi*t | |
| return vec3(r*math.cos(a),0,r*math.sin(a)) | |
| end | |
| local trackNormal = function(t) | |
| local a = math.pi*t | |
| return vec3( | |
| math.sin(a)*math.cos(2*a), | |
| math.cos(a), | |
| math.sin(a)*math.sin(2*a)) | |
| end | |
| return trackFunction,trackNormal,0.5,-0.5 | |
| end | |
| function Tracks.loop(p,q,r,h) | |
| local r = r or 30 | |
| local h = h or 10 | |
| local w = vec3(0,50,0) | |
| local trackFunction = function(t) | |
| local a = 2*math.pi*t | |
| return vec3( | |
| r*math.cos(a), | |
| h*math.sin(p*a), | |
| r*math.sin(q*a)) | |
| end | |
| local trackNormal = function(t) | |
| return w - trackFunction(t) | |
| end | |
| return trackFunction,trackNormal,h,-h | |
| end | |
| cmodule.gexport { | |
| tangent = tangent, | |
| Tracks = Tracks, | |
| TrackPoints = TrackPoints | |
| } | |
| --]==] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment