SpaceX-type gage using Mesh and Shader crashes.

I just completed a class definition of a gage with a circular dial with a “bar” that progresses clockwise as the value increases.

The mesh and shader technique originated as a function that defined 180 degrees of a dial and was the work of someone else. I redefined it as a class that would display a full 360 degrees and added additional graphics.

When I try and display four such dials on the screen at the same time the program crashes within two to three minutes.

QUESTION: Is the mesh/shader technique too taxing on the system, is there an inherent bug with mesh/shader or am I doing something wrong?

I’m running all this on a 3rd gen iPad (32Gigs with 2.5Gigs available), OS ver 9.3.5 using a version of Codea that has not been updated in forever due to the age of the iPad.

Thank you for any tips or solutions to this problem.

-- SpaceX-type dials using Mesh and Shader
-- Version 4
-- NOTE: Code is best viewed in landscape mode.



supportedOrientations(LANDSCAPE_ANY) -- preferred orientation
displayMode(STANDARD)


function setup()
    X = WIDTH/2
    Y = HEIGHT/2
    backColor = color(40,40,50)
    

    FPS,MAX,MIN = 0,0,200     -- For frames-per-second calc.   
    
    parameter.number("spdFrac",   0, 1, 0)
--[[
    parameter.number("altFrac",   0, 1, 0)
    parameter.number("tempFrac",  0, 1, 0)
    parameter.number("pressFrac", 0, 1, 0)
--]]
    
    -- NOTE: If dial radii and colors are all identical then 
    -- define them here ahead of Speedo variable defs.
    dialRad     = 150
    secondColor = color(127, 127, 127, 255) 
    primeColor  = color(0,255,0)    

    -- Speedo definitions:     
    -- "speed"
    title  = "Speed"
    spdMax = 500
    speedLoc = vec2(200,HEIGHT-200)
    --speedLoc = vec2(X,Y) -- for testing
    rad    = dialRad
    c1     = secondColor
    c2     = primeColor
        speed = Speedo(title,rad,c1,c2)
    
--[[
    -- "altitude"
    title  = "Altitude"
    altMax = 10000
    altitudeLoc = vec2(WIDTH-200,HEIGHT-200)
    --rad    = dialRad
    --c1     = secondColor
    --c2     = primeColor
        altitude = Speedo(title,rad,c1,c2)
   

    -- "temp"
    title   = "Temp"
    tempMax = 212
    tempLoc = vec2(200,200)
    --rad     = dialRad
    --c1      = secondColor
    --c2      = primeColor
        temp = Speedo(title,rad,c1,c2)
    

    -- "pressure"
    title    = "Pressure"
    pressMax = 500
    pressLoc = vec2(WIDTH-200, 200)
    --rad      = dialRad
    --c1       = secondColor
    --c2       = primeColor
        pressure = Speedo(title,rad,c1,c2)
--]]
          
end -- end of setup()




function draw()

    background(backColor)

    -- massage speed-data
    spdVal = spdMax*spdFrac
    ea1 = (360*spdFrac-180)*-1
    sa2 = ea1
    pushMatrix()
    translate(speedLoc.x,speedLoc.y)
        speed:draw(spdVal,ea1,sa2)
    popMatrix()

--[[
    -- massage altitude-data
    altVal = altMax*altFrac
    ea1 = (360*altFrac-180)*-1
    sa2 = ea1
    pushMatrix()
    translate(altitudeLoc.x,altitudeLoc.y)
        altitude:draw(altVal,ea1,sa2)
    popMatrix()


    -- massage temp-data
    tempVal = tempMax*tempFrac
    ea1 = (360*tempFrac-180)*-1
    sa2 = ea1
    pushMatrix()
    translate(tempLoc.x,tempLoc.y)
        temp:draw(tempVal,ea1,sa2)
    popMatrix()

        
    -- massage altitude-data
    pressVal = pressMax*pressFrac
    ea1 = (360*pressFrac-180)*-1
    sa2 = ea1
    pushMatrix()
    translate(pressLoc.x,pressLoc.y)
        pressure:draw(pressVal,ea1,sa2)
    popMatrix()
--]]

    MaxMin() -- frames-per-sec calculation         
    
end


function MaxMin()
-- Calculate frames-per-second
    
    FPS = 1/DeltaTime
    if FPS > MAX then
        MAX = FPS
    elseif FPS < MIN then
        MIN = FPS
    end
        
    fill(255, 255, 255, 255/2) 
    fontSize(20)
    text(string.format("FPS = %.0f", FPS,FPS), X, 60)
    text(string.format("MAX = %.0f", MAX,MAX), X, 40)
    text(string.format("MIN = %.0f", MIN,MIN), X, 20)
    
end



Speedo = class()
-- Mesh m1 and m2 definition by others but the rest is by Scotty.
-- Speedo is my term for any type of dial that looks like it belongs on a SpaceX-type display.

function Speedo:init(title, rad, c1, c2)
    -- incoming args:
    self.title  = title
    self.rad    = rad
    self.c1     = c1
    self.c2     = c2
    
    -- gage loc
    self.x      =    0
    self.y      =    0
    -- range of gage
    self.sa1    = -180
    self.ea2    =  180
    -- for min/max lines
    self.maxAng  =  185
    self.minAng  = -185
    -- min/max flags
    self.minFlag = 0
    self.maxFlag = 0
    -- min/max values
    self.minValue = 0
    self.maxValue = 0

end


function Speedo:draw(val,ea1,sa2)
    
    self.val  = val
    self.ea1  = ea1
    self.sa2  = sa2 - .1  -- NOTE: Without subtracting a small amount from sa2 the 
                          -- resulting dial is displayed fully lite up when dial value is 
                          -- at zero. This remedies that condition.


    local m1 = mesh()
    local m2 = mesh()

    local sWidth = self.rad * .25
    local size   = (1 - sWidth/self.rad) * 0.5
        
    if c1 == nil then c1 = color(background())  end

    -- Secondary, or background,  mesh
    m1:addRect(self.x, self.y, self.rad * 2, self.rad * 2)
    m1.shader       = shader("Patterns:Arc")
    m1.shader.size  = size               -- size
    m1.shader.color = self.c1            -- color
    m1.shader.a1    = math.rad(self.sa1) -- start angle
    m1.shader.a2    = math.rad(self.ea1) -- end angle   
    m1:draw()

    -- Primary mesh
    m2:addRect(self.x, self.y, self.rad * 2, self.rad * 2)
    m2.shader       = shader("Patterns:Arc")
    m2.shader.size  = size
    m2.shader.color = self.c2
    m2.shader.a1    = math.rad(self.sa2)
    m2.shader.a2    = math.rad(self.ea2)
    m2:draw()
    
    -- Ellipses
    pushStyle()
    stroke(self.c2)
    strokeWidth(self.rad*.015) -- 1.5% of dial rad
    noFill()
    ellipseMode(CENTER)
    ellipse(self.x,self.y, (self.rad*2+sWidth/2.25)+strokeWidth())
    ellipse(self.x,self.y, (self.rad*2-sWidth*2.25))
    
    -- Text
    fill(c2)
    fill(69, 255, 0, 77)
    fontSize(sWidth)
    text(self.title, self.x,self.y+sWidth/2)
    text(string.format("%.0f",self.val), self.x,self.y-sWidth/2)
    
    -- min-max values
    fontSize(sWidth/2)
    fill(38, 0, 255, 255)
    text(string.format("Max = %.0f",self.maxValue), self.x,self.y+sWidth*2)
    fill(255, 0, 0, 255)
    text(string.format("Min = %.0f",self.minValue), self.x,self.y-sWidth*2)
    fontSize(sWidth)

       
    -- Rotatable value line with max/min value.
    -- 180 is the "low point" of dial while -180 is the "high point".
    -- This seems backwards but that is how the dial was designed.
    if self.ea1 < self.maxAng then        -- high-point is increasing
        self.maxAng = self.ea1            -- save max angle
        self.maxValue = self.val          -- save max value
        if self.ea1 == self.maxAng then
            self.maxFlag = 1
        end
    elseif self.ea1 > self.maxAng then    -- value decreasing from high point
        self.maxFlag = 0
        self.minFlag = 1
    end
    
    if self.maxFlag == 0 then 
        if self.ea1 > self.minAng then    -- low-point is decreasing
            self.minAng   = self.ea1      -- save min angle
            self.minValue = self.val      -- save min value
        end
    end
    
        
    lineCapMode(SQUARE)
    strokeWidth(strokeWidth()*1.5)
        
    -- Display max line
    pushMatrix()
    stroke(0, 0, 255, 255)
    rotate(self.maxAng+180)
    line(self.x-self.rad, self.y,  self.x-self.rad+sWidth, self.y)
    popMatrix()
      

    -- Display "value" line
    pushMatrix()
    stroke(0, 128, 0, 255)
    rotate(self.ea1+180)
    line(self.x-self.rad, self.y,  self.x-self.rad+sWidth, self.y)
    popMatrix()
 
       
    -- Display "Zero" line
    stroke(0, 0, 0, 255)     
    line(self.x-self.rad, self.y,  self.x-self.rad+sWidth, self.y)
    
 
    -- Display min line
    if self.minFlag == 1 then
        pushMatrix()
        stroke(255, 0, 0, 255)
        rotate(self.minAng+180)
        line(self.x-self.rad, self.y,  self.x-self.rad+sWidth, self.y)
        popMatrix()
    end
      
    --[[
    -- Display values for testing only:
    fontSize(20)
    fill(255, 255, 255, 255)
    vLoc = self.rad/2
    vInc = 25

    vLoc = vLoc-vInc
    text("ea1 = "    ..string.format("%.f",self.ea1), self.x, self.y+vLoc)
    vLoc = vLoc-vInc
    text("minAng = " ..string.format("%.f",self.minAng), self.x, self.y+vLoc)
    vLoc = vLoc-vInc
    text("maxAng = " ..string.format("%.f",self.maxAng), self.x, self.y+vLoc)
    vLoc = vLoc-vInc
    text("minFlag = "..self.minFlag, self.x, self.y+vLoc)
    vLoc = vLoc-vInc
    text("maxFlag = "..self.maxFlag, self.x, self.y+vLoc)
    --]]
    
    popStyle()

end

you are creating the meshes every draw cycle, shouldn’t you do it just once during the init and then save it.

@piinthesky thanks for the reply.

Are you suggesting I place m1 = mesh() and m2 = mesh() in Speedo:init() rather than Speedo:draw()? I’ve just tried that and the performance is horrible. Very slow response from the parameter sliders – even with one dial. There must be something I’m missing from your suggestion.

Again, I apologize for my out-sized program size. It was with four dials on the screen that this problem became evident.

Thanks again.

@Scotty I thought I’d try this just for something to do. It’s not exactly like yours, but kinda close.

viewer.mode=STANDARD

function setup()
    m=mesh()  
    a1=arc(200,250,"Speed",color(255,0,0))
    a2=arc(200,600,"Altitude",color(0,255,0))
    a3=arc(600,250,"Temp",color(0,0,255))
    a4=arc(600,600,"Pressure",color(255,255,0))
    parameter.integer("sp",0,360,0)
    parameter.integer("al",0,360,0)
    parameter.integer("te",0,360,0)
    parameter.integer("pr",0,360,0)
end

function draw()
    background(135, 224, 216)
    a1:draw(sp)
    a2:draw(al)
    a3:draw(te)
    a4:draw(pr)
end

arc=class()

function arc:init(xPos,yPos,name,col)
    self.x=xPos
    self.y=yPos
    self.r=90
    self.t=40
    self.name=name
    self.c=col
    self.max=0
    self.min=0
    self.hold=0
end

function arc:draw(val) 
    fill(100)
    stroke(255)
    strokeWidth(2)
    ellipse(self.x,self.y,(self.r+self.t)*2+8)
    fill(0)
    ellipse(self.x,self.y,self.r*2)
    fontSize(30)
    fill(255)
    text(self.name,self.x,self.y+20)
    text(val,self.x,self.y-20)    
    if val<self.hold then
        self.min=val        
    elseif val>self.max then
        self.max=val
    end
    self.hold=val    
    fill(255, 148, 0)
    fontSize(15)
    text("Max = "..self.max,self.x,self.y+60)
    text("Min = "..self.min,self.x,self.y-60)
    local tab1={}
    for ang=0,val do
        local x1=math.cos(math.rad(180-ang-1))*self.r+self.x
        local y1=math.sin(math.rad(180-ang-1))*self.r+self.y
        local x2=math.cos(math.rad(180-ang-1))*(self.r+self.t)+self.x
        local y2=math.sin(math.rad(180-ang-1))*(self.r+self.t)+self.y
        local x3=math.cos(math.rad(180-ang))*self.r+self.x
        local y3=math.sin(math.rad(180-ang))*self.r+self.y
        local x4=math.cos(math.rad(180-ang))*(self.r+self.t)+self.x
        local y4=math.sin(math.rad(180-ang))*(self.r+self.t)+self.y
        table.insert(tab1,vec2(x1,y1))
        table.insert(tab1,vec2(x2,y2))
        table.insert(tab1,vec2(x4,y4))
        table.insert(tab1,vec2(x1,y1))
        table.insert(tab1,vec2(x3,y3))
        table.insert(tab1,vec2(x4,y4))
    end
    m.vertices=tab1
    m:setColors(self.c)
    m:draw()
    stroke(0)
    strokeWidth(4)
    self:minMax(self.min)
    self:minMax(self.max)
end

function arc:minMax(val)
    x1=math.cos(math.rad(180-val-1))*(self.r+2)+self.x
    y1=math.sin(math.rad(180-val-1))*(self.r+2)+self.y
    x2=math.cos(math.rad(180-val-1))*(self.r+38)+self.x
    y2=math.sin(math.rad(180-val-1))*(self.r+38)+self.y
    line(x1,y1,x2,y2)
end

Thanks @dave1707 for this example and, yes, it is very much like my attempt in appearance. I’ve made a couple of tweeks:

  1. Mods to code that keeps track on max and min values. (I want to save the max AND min during the running of the program rather than resetting the min.)
  2. I added an additional “arc” that shows the frames-per-second just to demonstrate the sending of calculated values rather than a parameter slider value.

And thank you @piinthesky for your suggestion as to where to init the mesh. It all makes sense now.


--viewer.mode=STANDARD
supportedOrientations(LANDSCAPE_ANY)
displayMode(STANDARD)

function setup()

    m=mesh()
    
    margin=175
    a1=arc(margin,HEIGHT-margin,"Speed", color(255,0,0))
    a2=arc(WIDTH-margin,HEIGHT-margin,"Altitude", color(0,255,0))
    a3=arc(margin,margin,"Temp", color(0,0,255))
    a4=arc(WIDTH-margin,margin,"Pressure", color(255,255,0))
    a5=arc(WIDTH/2,HEIGHT/2,"FPS", color(255,102,34,255)) -- frames per sec
    
    parameter.integer("sp",0,360,1)
    parameter.integer("al",0,360,1)
    parameter.integer("te",0,360,1)
    parameter.integer("pr",0,360,1)
end

function draw()
    background(135, 224, 216)
     
    a1:draw(sp)
    a2:draw(al)
    a3:draw(te)
    a4:draw(pr)
    a5:draw(math.floor(1/DeltaTime)) -- frames per second
    
end


arc=class()

function arc:init(xPos,yPos,name,col)
    self.x=xPos
    self.y=yPos
    self.r=90
    self.t=40
    self.name=name
    self.c=col
    
    self.max=0
    self.min=360
    self.hold=0
    -- new
    self.maxFlag = 0
    self.minFlag = 0
end

function arc:draw(val)
    fill(100)
    stroke(255)
    strokeWidth(2)
    ellipse(self.x,self.y,(self.r+self.t)*2+8)
    fill(0)
    ellipse(self.x,self.y,self.r*2)
    fontSize(30)
    fill(255)
    text(self.name,self.x,self.y+20)
    text(string.format("%.f",val),self.x,self.y-20)
    
-- Sets record max and min:
    if val > self.max then
        self.max = val
        if val == self.max then
            self.maxFlag = 1
        end
    elseif val < self.max then
        self.maxFlag = 0
        self.minFlag = 1
    end
    
    if self.maxFlag == 0 then
        if val < self.min then
            self.min = val
        end
    end
    self.hold=val
        
--[[ Saves max but resets min (original):
    if val<self.hold then
        self.min=val        
    elseif val>self.max then
        self.max=val
    end
    self.hold=val
--]]
        
    fill(255, 148, 0)
    fontSize(15)
    text(string.format("Max = %.f",self.max),self.x,self.y+60)
    text(string.format("Min = %.f",self.min),self.x,self.y-60)
    
    local tab1={}
    for ang=0,val do
        local x1=math.cos(math.rad(180-ang-1))*self.r+self.x
        local y1=math.sin(math.rad(180-ang-1))*self.r+self.y
        local x2=math.cos(math.rad(180-ang-1))*(self.r+self.t)+self.x
        local y2=math.sin(math.rad(180-ang-1))*(self.r+self.t)+self.y
        local x3=math.cos(math.rad(180-ang))*self.r+self.x
        local y3=math.sin(math.rad(180-ang))*self.r+self.y
        local x4=math.cos(math.rad(180-ang))*(self.r+self.t)+self.x
        local y4=math.sin(math.rad(180-ang))*(self.r+self.t)+self.y
        table.insert(tab1,vec2(x1,y1))
        table.insert(tab1,vec2(x2,y2))
        table.insert(tab1,vec2(x4,y4))
        table.insert(tab1,vec2(x1,y1))
        table.insert(tab1,vec2(x3,y3))
        table.insert(tab1,vec2(x4,y4))
    end
    m.vertices=tab1
    m:setColors(self.c)
    m:draw()
    
    stroke(0)
    strokeWidth(4)
    self:minMax(self.min)
    self:minMax(self.max)
    self:minMax(self.hold)
    self:minMax(0)

end


function arc:minMax(val)
    x1=math.cos(math.rad(180-val-1))*(self.r+2)+self.x
    y1=math.sin(math.rad(180-val-1))*(self.r+2)+self.y 
    x2=math.cos(math.rad(180-val-1))*(self.r+38)+self.x
    y2=math.sin(math.rad(180-val-1))*(self.r+38)+self.y
    line(x1,y1,x2,y2)
    --fontSize(18)
    --fill(255, 255, 255, 255)
    --text(string.format("%.f",val),(x1+x2)/2,(y1+y2)/2)
end