diff --git a/Nasal/canvas/api.nas b/Nasal/canvas/api.nas new file mode 100644 index 000000000..ef230bf65 --- /dev/null +++ b/Nasal/canvas/api.nas @@ -0,0 +1,520 @@ +# Helper function to create a node with the first available index for the given +# path relative to the given node +# +var _createNodeWithIndex = func(node, path) +{ + # TODO do we need an upper limit? (50000 seems already seems unreachable) + for(var i = 0; i < 50000; i += 1) + { + var p = path ~ "[" ~ i ~ "]"; + if( node.getNode(p) == nil ) + return node.getNode(p, 1); + } + + debug.warn("Unable to get child (already 50000 exist)"); + + return nil; +}; + +# Internal helper +var _createColorNodes = func(parent, name) +{ + var node = parent.getNode(name, 1); + return [ node.getNode("red", 1), + node.getNode("green", 1), + node.getNode("blue", 1), + node.getNode("alpha", 1) ]; +}; + +var _setColorNodes = func(nodes, color) +{ + if( typeof(nodes) != "vector" ) + { + debug.warn("This element doesn't support setting color"); + return; + } + + if( size(color) == 1 ) + color = color[0]; + + if( typeof(color) != "vector" ) + return debug.warn("Wrong type for color"); + + if( size(color) < 3 or size(color) > 4 ) + return debug.warn("Color needs 3 or 4 values (RGB or RGBA)"); + + for(var i = 0; i < size(color); i += 1) + nodes[i].setDoubleValue( color[i] ); + + if( size(color) == 3 ) + # default alpha is 1 + nodes[3].setDoubleValue(1); +}; + +var _arg2valarray = func +{ + while ( typeof(arg) == "vector" + and size(arg) == 1 and typeof(arg[0]) == "vector" ) + arg = arg[0]; + return arg; +} + +# Transform +# ============================================================================== +# A transformation matrix which is used to transform an #Element on the canvas. +# The dimensions of the matrix are 3x3 where the last row is always 0 0 1: +# +# a c e +# b d f +# 0 0 1 +# +# See http://www.w3.org/TR/SVG/coords.html#TransformMatrixDefined for details. +# +var Transform = { + new: func(node, vals = nil) + { + var m = { + parents: [Transform], + a: node.getNode("a", 1), + b: node.getNode("b", 1), + c: node.getNode("c", 1), + d: node.getNode("d", 1), + e: node.getNode("e", 1), + f: node.getNode("f", 1) + }; + + var use_vals = typeof(vals) == 'vector' and size(vals) == 6; + + # initialize to identity matrix + m.a.setDoubleValue(use_vals ? vals[0] : 1); + m.b.setDoubleValue(use_vals ? vals[1] : 0); + m.c.setDoubleValue(use_vals ? vals[2] : 0); + m.d.setDoubleValue(use_vals ? vals[3] : 1); + m.e.setDoubleValue(use_vals ? vals[4] : 0); + m.f.setDoubleValue(use_vals ? vals[5] : 0); + + return m; + }, + setTranslation: func + { + var trans = _arg2valarray(arg); + + me.e.setDoubleValue(trans[0]); + me.f.setDoubleValue(trans[1]); + + return me; + }, + setRotation: func(angle) + { + var s = math.sin(angle); + var c = math.cos(angle); + + me.a.setValue(c); + me.b.setValue(s); + me.c.setValue(-s); + me.d.setValue(c); + + return me; + }, + # Set scale (either as parameters or array) + # + # If only one parameter is given its value is used for both x and y + # setScale(x, y) + # setScale([x, y]) + setScale: func + { + var scale = _arg2valarray(arg); + + me.a.setDoubleValue(scale[0]); + me.d.setDoubleValue(size(scale) >= 2 ? scale[1] : scale[0]); + + return me; + }, + getScale: func() + { + # TODO handle rotation + return [me.a.getValue(), me.d.getValue()]; + } +}; + +# Element +# ============================================================================== +# Baseclass for all elements on a canvas +# +var Element = { + # Constructor + # + # @param parent Parent node (In the property tree) + # @param type Type string (Used as node name) + # @param name Name (Should be unique) + new: func(parent, type, name) + { + var m = { + parents: [Element], + _node: _createNodeWithIndex(parent, type), + }; + + if( name != nil ) + m._node.getNode("name", 1).setValue(name); + + return m; + }, + # Destructor (has to be called manually!) + del: func() + { + me._node.remove(); + }, + # Trigger an update of the element + # + # Elements are automatically updated once a frame, with a delay of one frame. + # If you wan't to get an element updated in the current frame you have to use + # this method. + update: func() + { + me._node.getNode("update", 1).setValue(1); + }, + # Create a new transformation matrix + # + # @param vals Default values (Vector of 6 elements) + createTransform: func(vals = nil) + { + var node = _createNodeWithIndex(me._node, "tf"); + return Transform.new(node, vals); + }, + # Shortcut for setting translation + setTranslation: func me._getTf().setTranslation(arg), + # Shortcut for setting rotation + setRotation: func(rot) me._getTf().setRotation(rot), + # Shortcut for setting scale + setScale: func me._getTf().setScale(arg), + # Shortcut for getting scale + getScale: func me._getTf().getScale(), + # Set the line/text color + # + # @param color Vector of 3 or 4 values in [0, 1] + setColor: func _setColorNodes(me.color, arg), + # Set the fill/background/boundingbox color + # + # @param color Vector of 3 or 4 values in [0, 1] + setColorFill: func _setColorNodes(me.color_fill, arg), + # Internal Transform for convenience transform functions + _getTf: func + { + if( me['_tf'] == nil ) + me['_tf'] = me.createTransform(); + return me._tf; + }, +}; + +# Group +# ============================================================================== +# Class for a group element on a canvas +# +var Group = { + new: func(parent, name) + { + return { parents: [Group, Element.new(parent, "group", name)] }; + }, + # Create a child of given type with specified name. + # type can be group, text + createChild: func(type, name = nil) + { + var factory = me._element_factories[type]; + + if( factory == nil ) + { + debug.dump("canvas.Group.createChild(): unknown type (" ~ type ~ ")"); + return nil; + } + + return factory(me._node, name); + }, + # Remove all children + removeAllChildren: func() + { + foreach(var type; keys(me._element_factories)) + me._node.removeChildren(type, 0); + } +}; + +# Text +# ============================================================================== +# Class for a text element on a canvas +# +var Text = { + new: func(parent, name) + { + var m = { + parents: [Text, Element.new(parent, "text", name)] + }; + m.color = _createColorNodes(m._node, "color"); + m.color_fill = _createColorNodes(m._node, "color-fill"); + return m; + }, + # Set the text + setText: func(text) + { + # add space because osg seems to remove last character if its a space + me._node.getNode("text", 1).setValue(typeof(text) == 'scalar' ? text ~ ' ' : ""); + }, + # Set alignment + # + # @param algin String, one of: + # left-top + # left-center + # left-bottom + # center-top + # center-center + # center-bottom + # right-top + # right-center + # right-bottom + # left-baseline + # center-baseline + # right-baseline + # left-bottom-baseline + # center-bottom-baseline + # right-bottom-baseline + # + setAlignment: func(align) + { + me._node.getNode("alignment", 1).setValue(align); + }, + # Set the font size + setSize: func(size, aspect = 1) + { + me._node.getNode("character-size", 1).setDoubleValue(size); + me._node.getNode("character-aspect-ratio", 1).setDoubleValue(aspect); + }, + # Set font (by name of font file) + setFont: func(name) + { + me._node.getNode("font", 1).setValue(name); + }, + # Enumeration of values for drawing mode: + TEXT: 1, # The text itself + BOUNDINGBOX: 2, # A bounding box (only lines) + FILLEDBOUNDINGBOX: 4, # A filled bounding box + ALIGNMENT: 8, # Draw a marker (cross) at the position of the text + # Set draw mode. Binary combination of the values above. Since I haven't found + # a bitwise or we have to use a + instead. + # + # eg. my_text.setDrawMode(Text.TEXT + Text.BOUNDINGBOX); + setDrawMode: func(mode) + { + me._node.getNode("draw-mode", 1).setValue(mode); + }, + # Set bounding box padding + setPadding: func(pad) + { + me._node.getNode("padding", 1).setValue(pad); + }, + # + getBoundingBox: func() + { + var bb = me._node.getNode("bounding-box"); + return [ bb.getChild("min-x").getValue(), + bb.getChild("min-y").getValue(), + bb.getChild("max-x").getValue(), + bb.getChild("max-y").getValue() ]; + } +}; + +# Path +# ============================================================================== +# Class for an (OpenVG) path element on a canvas +# +var Path = { + # Path segment commands (VGPathCommand) + VG_CLOSE_PATH: 0, + VG_MOVE_TO: 2, + VG_MOVE_TO_ABS: 2, + VG_MOVE_TO_REL: 3, + VG_LINE_TO: 4, + VG_LINE_TO_ABS: 4, + VG_LINE_TO_REL: 5, + VG_HLINE_TO: 6, + VG_HLINE_TO_ABS: 6, + VG_HLINE_TO_REL: 7, + VG_VLINE_TO: 8, + VG_VLINE_TO_ABS: 8, + VG_VLINE_TO_REL: 9, + VG_QUAD_TO: 10, + VG_QUAD_TO_ABS: 10, + VG_QUAD_TO_REL: 11, + VG_CUBIC_TO: 12, + VG_CUBIC_TO_ABS: 12, + VG_CUBIC_TO_REL: 13, + VG_SQUAD_TO: 14, + VG_SQUAD_TO_ABS: 14, + VG_SQUAD_TO_REL: 15, + VG_SCUBIC_TO: 16, + VG_SCUBIC_TO_ABS: 16, + VG_SCUBIC_TO_REL: 17, + VG_SCCWARC_TO: 18, + VG_SCCWARC_TO_ABS:18, + VG_SCCWARC_TO_REL:19, + VG_SCWARC_TO: 20, + VG_SCWARC_TO_ABS: 20, + VG_SCWARC_TO_REL: 21, + VG_LCCWARC_TO: 22, + VG_LCCWARC_TO_ABS:22, + VG_LCCWARC_TO_REL:23, + VG_LCWARC_TO: 24, + VG_LCWARC_TO_ABS: 24, + VG_LCWARC_TO_REL: 25, + + # Number of coordinates per command + num_coords: { + 0: 0, # VG_CLOSE_PATH + 2: 2, # VG_MOVE_TO + 4: 2, # VG_LINE_TO + 6: 1, # VG_HLINE_TO + 8: 1, # VG_VLINE_TO + 10: 4, # VG_QUAD_TO + 12: 6, # VG_CUBIC_TO + 14: 2, # VG_SQUAD_TO + 16: 4, # VG_SCUBIC_TO + 18: 5, # VG_SCCWARC_TO + 20: 5, # VG_SCWARC_TO + 22: 5, # VG_LCCWARC_TO + 24: 5 # VG_LCWARC_TO + }, + + # + new: func(parent, name) + { + var m = { + parents: [Path, Element.new(parent, "path", name)] + }; + m.color = _createColorNodes(m._node, "color"); + m.color_fill = _createColorNodes(m._node, "color-fill"); + return m; + }, + # Set the path data (commands and coordinates) + setData: func(cmds, coords) + { + me._node.removeChildren('cmd', 0); + me._node.removeChildren('coord', 0); + me._node.setValues({cmd: cmds, coord: coords}); + }, + setStrokeLineWidth: func(width) + { + me._node.getNode('stroke-width', 1).setDoubleValue(width); + }, + setStrokeDashPattern: func(pattern) + { + me._node.removeChildren('stroke-dasharray'); + + if( typeof(pattern) == 'vector' ) + me._node.setValues({'stroke-dasharray': pattern}); + else + debug.warn("setStrokeDashPattern: vector expected!"); + }, + setFill: func(fill) + { + me._node.getNode("fill", 1).setValue(fill); + } +}; + +# Element factories used by #Group elements to create children +Group._element_factories = { + "group": Group.new, + "text": Text.new, + "path": Path.new +}; + +# Canvas +# ============================================================================== +# Class for a canvas +# +var Canvas = { + # Place this canvas somewhere onto the object. Pass criterions for placement + # as a hash, eg: + # + # my_canvas.addPlacement({ + # "texture": "EICAS.png", + # "node": "PFD-Screen", + # "parent": "Some parent name" + # }); + # + # Note that we can choose whichever of the three filter criterions we use for + # matching the target object for our placement. If none of the three fields is + # given every texture of the model will be replaced. + addPlacement: func(vals) + { + var placement = _createNodeWithIndex(me.texture, "placement"); + placement.setValues(vals); + return placement; + }, + # Create a new group with the given name + # + # @param name Option name for the group + createGroup: func(name = nil) + { + return Group.new(me.texture, name); + }, + # Set the background color + # + # @param color Vector of 3 or 4 values in [0, 1] + setColorBackground: func _setColorNodes(me.color, arg) +}; + +# Create a new canvas. Pass parameters as hash, eg: +# +# var my_canvas = canvas.new({ +# "name": "PFD-Test", +# "size": [512, 512], +# "view": [768, 1024], +# "mipmapping": 1 +# }); +var new = func(vals) +{ + var m = { parents: [Canvas] }; + + m.texture = _createNodeWithIndex + ( + props.globals.getNode("canvas", 1), + "texture" + ); + m.color = _createColorNodes(m.texture, "color-background"); + m.texture.setValues(vals); + + return m; +}; + +# Get the first existing canvas with the given name +# +# @param name Name of the canvas +# @return #Canvas, if canvas with #name exists +# nil, otherwise +var get = func(name) +{ + var node_canvas = nil; + if( isa(name, props.Node) ) + node_canvas = name; + else if( typeof(name) == 'scalar' ) + { + var canvas_root = props.globals.getNode("canvas"); + if( canvas_root == nil ) + return nil; + + foreach(var c; canvas_root.getChildren("texture")) + { + if( c.getValue("name") == name ) + node_canvas = c; + } + } + + if( node_canvas == nil ) + { + debug.warn("Canvas not found: " ~ name); + return nil; + } + + return { + parents: [Canvas], + texture: node_canvas, + color: _createColorNodes(node_canvas, "color-background") + }; +};