/** * Copyright (C) 2014-2016 Triumph LLC * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ "use strict"; /** * Head-up display control module. * @name hud * @namespace * @exports exports as hud */ b4w.module["__hud"] = function(exports, require) { var m_graph = require("__graph"); var m_print = require("__print"); var m_subs = require("__subscene"); var START_POINT_X = 30; var START_POINT_Y = 80; var LINE_WIDTH = 20; var OFFSETS = [5, 30, 4, 10, 5, 3, 5]; var _canvas_context = null; // row,column var _carriage = [0, 0]; exports.init = function(canvas_elem) { var ctx = canvas_elem.getContext("2d"); set_style(ctx); _canvas_context = ctx; return ctx; } function set_style(ctx) { ctx.font = 'bold 15px Courier New'; ctx.textBaseline = 'middle'; ctx.shadowBlur = 0; ctx.shadowColor = null; } exports.update_dim = function() { if (_canvas_context) set_style(_canvas_context); } exports.reset = function() { var ctx = _canvas_context; if (!ctx) return; ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); ctx.fillStyle = 'rgba(0,0,0,0.5)'; ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height); reset_carriage(); } exports.show_debug_info = function(scenes, elapsed) { var ctx = _canvas_context; if (!ctx) return; ctx.textAlign = 'left'; ctx.fillStyle = 'rgba(0,255,0,255)'; print(" FPS", (1/elapsed).toFixed(2)); new_line(); var sum_bundles = 0; var sum_rcalls = 0; var sum_rtimes = 0; for (var i = 0; i < scenes.length; i++) { var sum = show_debug_info_scene(scenes[i]); sum_bundles += sum[0]; sum_rcalls += sum[1]; sum_rtimes += sum[2]; new_line(); } print(" ----------------------------------------------------------------"); print(" ", "TOTAL ACTIVE", "", "", sum_rcalls, "of", sum_bundles, " ", sum_rtimes.toFixed(3)); } function show_debug_info_scene(scene) { print(" SCENE \"" + scene["name"] + "\""); print(" Active Subscene Lamps Size RenderCalls Time"); if (!scene._render) { print("No INFO") return; } //print("subscenes", graph.length, "(act/pass/fict type size batches calls lights):"); var sum_bundles = 0; var sum_rcalls = 0; var sum_rtimes = 0; var next_slot = 2; var graph = scene._render.graph; m_graph.traverse(graph, function(node, attr) { var subs = attr; // fictional type if (subs.type == m_subs.SINK) return; var type = subs.type; var size = Math.round(subs.camera.width) + "x" + Math.round(subs.camera.height); var bundles = 0; for (var i = 0; i < subs.draw_data.length; i++) bundles += subs.draw_data[i].bundles.length; var rcalls = subs.debug_render_calls; if (subs.type == m_subs.MAIN_CUBE_REFLECT || subs.type == m_subs.MAIN_CUBE_REFLECT_BLEND) bundles *= 6; // active/passive var is_active = subs.do_render; var render_time = is_active ? subs.debug_render_time : 0; // NOTE: clear render time for non-enqueued subscenes, BTW they // shouldn't be active if they're not rendered if (!subs.enqueue) subs.debug_render_time = 0; var activity_prefix = is_active ? " (\u2713)" : " (\u2715)"; var label = m_subs.subs_label(subs); print(activity_prefix, label, subs.num_lights, size, rcalls, "of", bundles, " ", render_time.toFixed(3)); subs.debug_render_calls = 0; if (is_active) { sum_bundles += bundles; sum_rcalls += rcalls; sum_rtimes += render_time; } }); return [sum_bundles, sum_rcalls, sum_rtimes]; } function reset_carriage() { _carriage[0] = 0; _carriage[1] = 0; } function new_line() { _carriage[0]++; } exports.print = function() { var ctx = _canvas_context; if (!ctx) return; ctx.textAlign = 'left'; ctx.fillStyle = 'rgba(0,255,0,255)'; print.apply(Math, arguments); } /** * Print a line. * Uses global context and carriage. */ function print() { var x = START_POINT_X; var y = START_POINT_Y + _carriage[0] * LINE_WIDTH; var text = arguments[0]; for (var i = 1; i < arguments.length; i++) { var arg = arguments[i]; var num_spaces = OFFSETS[i] - String(arg).length; text += spaces(num_spaces) + arguments[i]; } new_line(); _canvas_context.fillText(text, x, y); } function spaces(n) { var s = ""; for (var i = 0; i < n; i++) { s += " "; } return s; } /** * @see http://www.html5canvastutorials.com/tutorials/html5-canvas-wrap-text-tutorial/ */ function wrap_text(context, text, x, y, maxWidth, lineHeight) { var words = text.split(" "); var line = ""; for (var n = 0; n < words.length; n++) { var testLine = line + words[n] + " "; var metrics = context.measureText(testLine); var testWidth = metrics.width; if (testWidth > maxWidth) { context.fillText(line, x, y); line = words[n] + " "; y += lineHeight; } else { line = testLine; } } context.fillText(line, x, y); } exports.plot_array = function(header, slot, arr, arg_min, arg_max, val_min, val_max) { var ctx = _canvas_context; if (!ctx) return; var box = create_strip_box(slot); box_split_v(box, 4, 5, box); ctx.textAlign = 'center'; ctx.fillStyle = '#00FF00'; _canvas_context.fillText(header, box_mid_h(box), box.y - 10); ctx.strokeStyle = '#00FF00'; ctx.lineWidth = 0.5; ctx.beginPath(); ctx.moveTo(box.x, box.y); ctx.lineTo(box.x + box.w, box.y); ctx.lineTo(box.x + box.w, box.y + box.h); ctx.lineTo(box.x, box.y + box.h); ctx.closePath(); ctx.stroke(); if (!val_min && !val_max) { // TODO: proper calc val_min = 1000000; val_max =-1000000; for (var i = 0; i < arr.length; i++) { val_min = Math.min(val_min, arr[i]); val_max = Math.max(val_max, arr[i]); } } ctx.textAlign = "right"; _canvas_context.fillText(String(val_min), box.x - 10, box.y + box.h); _canvas_context.fillText(String(val_max), box.x - 10, box.y); ctx.textAlign = "center"; _canvas_context.fillText(String(arg_min), box.x, box.y + box.h + 15); _canvas_context.fillText(String(arg_max), box.x + box.w, box.y + box.h + 15); ctx.strokeStyle = '#FF0000'; ctx.lineWidth = 1.5; ctx.beginPath(); var dx = box.w / (arr.length - 1); var dy = box.h / (val_max - val_min); for (var i = 0; i < arr.length; i++) { var x = box.x + dx * i; var y = (box.y + box.h) - (arr[i] - val_min) * dy; if (i == 0) ctx.moveTo(x, y); else ctx.lineTo(x, y); } ctx.stroke(); } function box_create() { return { x : 0, y : 0, w : 0, h : 0 } } function box_set(x, y, w, h, dest) { dest.x = x; dest.y = y; dest.w = w; dest.h = h; return dest; } function box_split_h(box, part, parts, dest) { var dw = box.w / parts; dest.x = box.x + dw * part; dest.y = box.y; dest.w = dw; dest.h = box.h; return dest; } function box_split_v(box, part, parts, dest) { var dh = box.h / parts; dest.x = box.x dest.y = box.y + dh * part; dest.w = box.w; dest.h = dh; return dest; } function box_trim_h(box, ratio, dest) { var dw = box.w * ratio; dest.x = box.x + dw; dest.y = box.y; dest.w = box.w - 2 * dw; dest.h = box.h; return dest; } function box_trim_v(box, ratio, dest) { var dh = box.h * ratio; dest.x = box.x; dest.y = box.y + dh; dest.w = box.w; dest.h = box.h - 2 * dh; return dest; } function box_mid_h(box) { return box.x + box.w / 2; } function box_mid_v(box) { return box.y + box.h / 2; } function box_debug_draw(box) { var ctx = _canvas_context; var ss = ctx.strokeStyle; var lw = ctx.lineWidth; ctx.strokeStyle = '#FF0000'; ctx.lineWidth = 0.5; ctx.beginPath(); ctx.moveTo(box.x, box.y); ctx.lineTo(box.x + box.w, box.y); ctx.lineTo(box.x + box.w, box.y + box.h); ctx.lineTo(box.x, box.y + box.h); ctx.closePath(); ctx.stroke(); ctx.strokeStyle = ss; ctx.lineWidth = lw; } function create_strip_box(slot) { var ctx = _canvas_context; var box = box_create(); box_set(0, 0, ctx.canvas.width, ctx.canvas.height, box); box_trim_h(box, 0.01, box); box_trim_v(box, 0.02, box); box_split_h(box, slot, 8, box); box_trim_h(box, 0.02, box); return box; } exports.draw_mixer_strip = function(id, is_active, slot, params, active_param, mute, solo) { var ctx = _canvas_context; if (!ctx) return; ctx.textAlign = "center"; ctx.fillStyle = "#00FF00"; ctx.font = 'bold 12px Courier'; var box_strip = create_strip_box(slot); var strip_title = is_active ? "[" + id + "]" : id; _canvas_context.fillText(strip_title, box_mid_h(box_strip), box_strip.y); if (mute >= 0) _canvas_context.fillText(mute ? "[M]" : "[ ]", box_mid_h(box_strip) - 15, box_strip.y + 30); if (solo >= 0) _canvas_context.fillText(solo ? "[S]" : "[ ]", box_mid_h(box_strip) + 15, box_strip.y + 30); var box_param = box_create(); for (var i = 0; i < params.length; i++) { var param = params[i]; var is_volume = (param[0] == "VOLUME"); if (is_volume) box_set(box_strip.x, box_strip.y + 350, box_strip.w, 250, box_param); else box_set(box_strip.x, box_strip.y + 50 + 50 * i, box_strip.w, box_strip.h, box_param); draw_param_bar(ctx, is_volume, box_param, param, i == active_param); } } function draw_param_bar(ctx, vertical, box, param, is_active) { var name = param[0]; var value = param[1]; var min = param[2]; var max = param[3]; var steps = param[4]; var is_log = param[5]; var step = (max - min) / steps; if (step < 1) var digits = Math.floor(1 / step - 0.00001).toFixed(0).length; else var digits = 0; if (is_log) var val_pos_factor = Math.log(value / min) / Math.log(max/min); else var val_pos_factor = (value - min) / (max - min); ctx.textAlign = "center"; if (is_active) ctx.fillText("["+name+"]", box_mid_h(box), box.y); else ctx.fillText(name, box_mid_h(box), box.y); ctx.strokeStyle = "#00FF00"; if (vertical) ctx.lineWidth = 3; else ctx.lineWidth = 1; if (vertical) { ctx.textAlign = "right"; ctx.fillText(max.toFixed(digits), box_mid_h(box)-10, box.y + 15); ctx.textAlign = "right"; ctx.fillText(min.toFixed(digits), box_mid_h(box)-10, box.y + box.h - 15); } else { ctx.textAlign = "right"; ctx.fillText(min.toFixed(digits), box.x+28, box.y + 15); ctx.textAlign = "left"; ctx.fillText(max.toFixed(digits), box.x+box.w-28, box.y + 15); } ctx.beginPath(); if (vertical) { var val_pos_y = box.y + 15 + (box.h - 30) * (1 - val_pos_factor); ctx.moveTo(box_mid_h(box), box.y + 15); ctx.lineTo(box_mid_h(box), box.y + box.h - 15); ctx.moveTo(box_mid_h(box) - 5, box.y + 15); ctx.lineTo(box_mid_h(box) + 5, box.y + 15); ctx.moveTo(box_mid_h(box) - 5, box.y + box.h - 15); ctx.lineTo(box_mid_h(box) + 5, box.y + box.h - 15); ctx.moveTo(box_mid_h(box) - 5, val_pos_y); ctx.lineTo(box_mid_h(box) + 5, val_pos_y); } else { var val_pos_x = box.x + 30 + (box.w - 60) * val_pos_factor; ctx.moveTo(box.x + 30, box.y + 15); ctx.lineTo(box.x + box.w - 30, box.y + 15); ctx.moveTo(box.x + 30, box.y + 15 - 5); ctx.lineTo(box.x + 30, box.y + 15 + 5); ctx.moveTo(box.x + box.w - 30, box.y + 15 - 5); ctx.lineTo(box.x + box.w - 30, box.y + 15 + 5); ctx.moveTo(val_pos_x, box.y + 15 - 5); ctx.lineTo(val_pos_x, box.y + 15 + 5); } ctx.stroke(); // NOTE: temporary hack to append bandwidth in octaves if (name == "EQ_Q") { // see http://www.sengpielaudio.com/calculator-bandwidth.htm var Q = value; var Y = 1 + 1 / (2*Q*Q) + Math.sqrt((2 + 1/(Q*Q)) * (2 + 1/(Q*Q)) / 4 - 1); var N = Math.log(Y) / Math.log(2); if (vertical) { ctx.textAlign = "left"; ctx.fillText(value.toFixed(digits), box_mid_h(box) + 10, val_pos_y); ctx.textAlign = "left"; ctx.fillText("("+N.toFixed(digits+1)+")", box_mid_h(box) + 10, val_pos_y + 10); } else { ctx.textAlign = "right"; ctx.fillText(value.toFixed(digits), val_pos_x, box.y + 30); ctx.textAlign = "left"; ctx.fillText("("+N.toFixed(digits+1)+")", val_pos_x, box.y + 30); } } else { if (vertical) { ctx.textAlign = "left"; ctx.fillText(value.toFixed(digits), box_mid_h(box) + 10, val_pos_y); } else { ctx.textAlign = "center"; ctx.fillText(value.toFixed(digits), val_pos_x, box.y + 30); } } } }