/** * 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"; /** * Generates submeshes with primitive geometry. * @name primitives * @namespace * @exports exports as primitives */ b4w.module["__primitives"] = function(exports, require) { var m_cfg = require("__config"); var m_geom = require("__geometry"); var m_print = require("__print"); var m_util = require("__util"); var cfg_def = m_cfg.defaults; exports.generate_line = function() { var submesh = m_geom.init_submesh("LINE"); var va_frame = m_util.create_empty_va_frame(); va_frame["a_position"] = new Float32Array(3); va_frame["a_direction"] = new Float32Array(3); submesh.va_frames[0] = va_frame; submesh.indices = new Uint32Array(1); submesh.base_length = 1; return submesh; } exports.generate_plane = function(x_size, y_size) { var grid_submesh = generate_grid(2, 2, x_size, y_size); grid_submesh.name = "PLANE"; return grid_submesh; } exports.generate_grid = generate_grid; /** * @methodOf primitives */ function generate_grid(x_subdiv, y_subdiv, x_size, y_size) { var indices = []; var positions = []; var texcoords = []; var tbn_quats = []; var delta_x = (2 * x_size) / (x_subdiv - 1); var delta_y = (2 * y_size) / (y_subdiv - 1); for (var i = 0; i < x_subdiv; i++) { var x = -x_size + i * delta_x; for (var j = 0; j < y_subdiv; j++) { var y = -y_size + j * delta_y; positions.push(x, y, 0); tbn_quats.push(0, 0, 0, 1); texcoords.push(i / (x_subdiv - 1), j / (y_subdiv -1)); if (i && j) { var idx0 = i * y_subdiv + j; var idx1 = idx0 - 1; var idx2 = (i - 1) * y_subdiv + j; var idx3 = idx2 - 1; indices.push(idx0, idx1, idx2); indices.push(idx1, idx3, idx2); } } } // construct submesh var va_frame = m_util.create_empty_va_frame(); va_frame["a_position"] = new Float32Array(positions); va_frame["a_tbn_quat"] = new Float32Array(tbn_quats); var submesh = m_geom.init_submesh("GRID_PLANE"); submesh.va_frames[0] = va_frame; submesh.va_common["a_texcoord"] = new Float32Array(texcoords); submesh.indices = new Uint32Array(indices); submesh.base_length = positions.length/3; return submesh; } /** * Extract water submesh */ exports.generate_cascaded_grid = function(num_cascads, subdivs, detailed_dist) { var min_casc_size = detailed_dist / Math.pow(2, num_cascads - 1); var x_size = min_casc_size; var y_size = min_casc_size; var x_subdiv = subdivs + 1; var y_subdiv = subdivs + 1; var indices = []; var positions = []; var tbn_quats = []; var prev_x = 0; var prev_y = 0; var last_added_ind = -1; function is_merged_vertex(i, j) { if ( (i % 2 == 1 && (j == 0 || j == y_subdiv - 1)) || (j % 2 == 1 && (i == 0 || i == x_subdiv - 1)) ) return true; else return false; } var prev_utmost_verts = []; // prev cascad utmost verts (x, y, ind) for (var c = 0; c < num_cascads; c++) { var delta_x = (2 * x_size) / (x_subdiv - 1); var delta_y = (2 * y_size) / (y_subdiv - 1); var cur_utmost_verts = []; // current cascad utmost verts (x, y, ind) var casc_indices = []; // current cascad indices var all_skipped = 0; for (var i = 0; i < x_subdiv; i++) { var x = -x_size + i * delta_x; var indices_in_row = []; for (var j = 0; j < y_subdiv; j++) { var y = -y_size + j * delta_y; // process vertices only otside previous cascad if (!(x > -prev_x && x < prev_x && y > -prev_y && y < prev_y)) { var coinciding_ind = null; // check if there exist a vertex with the same coords for (var k = 0; k < prev_utmost_verts.length; k+=3) { if (x == prev_utmost_verts[k] && y == prev_utmost_verts[k+1]) { coinciding_ind = prev_utmost_verts[k+2]; break; } } if (coinciding_ind !== null) { var idx0 = coinciding_ind; } else var idx0 = last_added_ind + 1; // push to positions array if needed if ( !is_merged_vertex(i, j) ) { if (coinciding_ind == null) { if ((j == 0 || j == y_subdiv - 1 || i == 0 || i == x_subdiv - 1)) { if (c == num_cascads - 1) var cascad_step = delta_x; else var cascad_step = 2 * delta_x; cur_utmost_verts.push(x, y, idx0); } else var cascad_step = delta_x; positions.push(x, y, cascad_step); tbn_quats.push(0.707,0,0,0.707); last_added_ind++; } indices_in_row.push(idx0); } else { indices_in_row.push(-2); // is odd utmost all_skipped++; } if (i && j) { // for not utmost vertices if ( i == 1 ) { // 2-nd column if (is_merged_vertex(i-1, j)) { if ( j > 1) { var idx1 = idx0 - 1; var idx2 = casc_indices[i-1][j+1]; var idx3 = idx2 - 1; indices.push(idx3, idx1, idx0); indices.push(idx2, idx3, idx0); } else { var idx2 = casc_indices[i-1][j+1]; var idx3 = idx2 - 1; indices.push(idx2, idx3, idx0); } } else if (!is_merged_vertex(i, j)) { var idx1 = idx0 - 1; var idx2 = casc_indices[i-1][j]; indices.push(idx2, idx1, idx0); } } else if ( i == x_subdiv - 1 ) { // last column if (!is_merged_vertex(i, j)) { if (j == y_subdiv - 1) { // build lower-right corner var idx1 = idx0 - 1; var idx2 = casc_indices[i-1][j-1]; var idx3 = casc_indices[i-2][j]; indices.push(idx2, idx1, idx0); indices.push(idx3, idx2, idx0); } else { var idx1 = idx0 - 1; var idx2 = casc_indices[i-1][j]; var idx3 = idx2 - 1; var idx4 = idx2 + 1; indices.push(idx3, idx1, idx0); indices.push(idx2, idx3, idx0); indices.push(idx4, idx2, idx0); if (j == 2) { // build upper-right corner idx4 = casc_indices[i-2][j-2]; indices.push(idx3, idx4, idx1); } } } } else if ( j == 1 ) { // 2-nd row if (!is_merged_vertex(i, j-1)) { var idx1 = indices_in_row[j-1]; var idx2 = casc_indices[i-1][j]; var idx3 = casc_indices[i-2][j-1]; indices.push(idx2, idx1, idx0); indices.push(idx2, idx3, idx1); } else { var idx1 = casc_indices[i-1][j]; var idx2 = casc_indices[i-1][j-1]; indices.push(idx1, idx2, idx0); } } else if ( j == y_subdiv - 1 ) { // last row if (!is_merged_vertex(i, j)) { var idx1 = indices_in_row[j-1]; var idx2 = casc_indices[i-1][j-1]; var idx3 = casc_indices[i-2][j]; indices.push(idx2, idx1, idx0); indices.push(idx3, idx2, idx0); } } else if (casc_indices[i-1][j] != -1 && casc_indices[i-1][j-1] != -1 && indices_in_row[j-1] != -1) { var idx1 = indices_in_row[j-1]; var idx2 = casc_indices[i-1][j]; var idx3 = casc_indices[i-1][j-1]; indices.push(idx2, idx1, idx0); indices.push(idx2, idx3, idx1); if (j == y_subdiv - 2 && is_merged_vertex(i, j+1)) { var idx4 = casc_indices[i-1][j+1] indices.push(idx4, idx2, idx0); } } else if (j == y_subdiv - 2 && is_merged_vertex(i, j+1)) { var idx2 = casc_indices[i-1][j]; var idx4 = casc_indices[i-1][j+1] indices.push(idx4, idx2, idx0); } } } else { indices_in_row.push(-1); all_skipped++; } } casc_indices.push(indices_in_row); } prev_utmost_verts = cur_utmost_verts; prev_x = x_size; prev_y = y_size; x_size *= 2; y_size *= 2; } // generate outer cascad from 8 vertices [Optional] var required_mesh_size = 20000; if (prev_x < required_mesh_size) { var casc_step = -(2 * prev_x) / (x_subdiv - 1); positions.push(-required_mesh_size, -required_mesh_size, casc_step); positions.push(-required_mesh_size, required_mesh_size, casc_step); positions.push(-prev_x, -prev_y, casc_step); positions.push(-prev_x, prev_y, casc_step); positions.push( required_mesh_size, -required_mesh_size, casc_step); positions.push( required_mesh_size, required_mesh_size, casc_step); positions.push( prev_x, -prev_y, casc_step); positions.push( prev_x, prev_y, casc_step); var idx0 = last_added_ind + 1; indices.push(idx0 + 1, idx0 + 2, idx0 + 3, idx0 + 1, idx0 + 0, idx0 + 2, idx0 + 2, idx0 + 4, idx0 + 6, idx0 + 2, idx0 + 0, idx0 + 4, idx0 + 1, idx0 + 7, idx0 + 5, idx0 + 1, idx0 + 3, idx0 + 7, idx0 + 7, idx0 + 4, idx0 + 5, idx0 + 6, idx0 + 4, idx0 + 7); for (var i = 0; i < 8; i++) tbn_quats.push(0.707,0,0,0.707); } // construct submesh var va_frame = m_util.create_empty_va_frame(); va_frame["a_position"] = new Float32Array(positions); va_frame["a_tbn_quat"] = new Float32Array(tbn_quats); var submesh = m_geom.init_submesh("MULTIGRID_PLANE"); submesh.va_frames[0] = va_frame; submesh.indices = new Uint32Array(indices); submesh.base_length = positions.length/3; // debug wireframe mode if (cfg_def.water_wireframe_debug) { m_geom.submesh_drop_indices(submesh, 1, true); va_frame["a_polyindex"] = m_geom.extract_polyindices(submesh); } return submesh; } /** * Generate submesh for shadeless rendering (w/o normals) * verts must be CCW if you look at the front face of triangle */ exports.generate_from_triangles = function(verts) { var len = verts.length; if (len % 3) m_util.panic("Wrong array"); var indices = []; var positions = []; for (var i = 0; i < len; i+=3) { var v1 = verts[i]; var v2 = verts[i+1]; var v3 = verts[i+2]; add_vec3_to_array(v1, positions); add_vec3_to_array(v2, positions); add_vec3_to_array(v3, positions); indices.push(i, i+1, i+2); } // construct submesh var va_frame = m_util.create_empty_va_frame(); va_frame["a_position"] = new Float32Array(positions); var submesh = m_geom.init_submesh("FROM_TRIANGLES"); submesh.va_frames[0] = va_frame; submesh.indices = new Uint32Array(indices); submesh.base_length = positions.length/3; return submesh; } exports.generate_from_quads = generate_from_quads; /** * Generate submesh for shadeless rendering (w/o normals). * verts must be CCW if you look at the front face of quad * @methodOf primitives */ function generate_from_quads(verts) { var len = verts.length; if (len % 4) m_util.panic("Wrong array"); var indices = []; var positions = []; for (var i = 0; i < len; i+=4) { var v1 = verts[i]; var v2 = verts[i+1]; var v3 = verts[i+2]; var v4 = verts[i+3]; add_vec3_to_array(v1, positions); add_vec3_to_array(v2, positions); add_vec3_to_array(v3, positions); add_vec3_to_array(v4, positions); indices.push(i, i+1, i+2); indices.push(i, i+2, i+3); } // construct submesh var va_frame = m_util.create_empty_va_frame(); va_frame["a_position"] = new Float32Array(positions); var submesh = m_geom.init_submesh("FROM_QUADS"); submesh.va_frames[0] = va_frame; submesh.indices = new Uint32Array(indices); submesh.base_length = positions.length/3; return submesh; } /** * Generate frustum submesh from submesh corners. *

NOTE1: corners must be near and far planes *

NOTE2: near plane - CCW, far plane - CW (from viewer point) *

NOTE3: left buttom vertex of near and far plane are joined */ exports.generate_frustum = function(corners) { var corners = m_util.vectorize(corners, []); // TODO: implement simple method to generate frustum geometry var quads = []; // near quad quads.push(corners[0], corners[1], corners[2], corners[3]); // left quad quads.push(corners[0], corners[3], corners[5], corners[4]); // top quad quads.push(corners[3], corners[2], corners[6], corners[5]); // right quad quads.push(corners[1], corners[7], corners[6], corners[2]); // buttom quad quads.push(corners[0], corners[4], corners[7], corners[1]); // far quad quads.push(corners[4], corners[5], corners[6], corners[7]); var submesh = generate_from_quads(quads); submesh.name = "FRUSTUM"; return submesh; } exports.generate_fullscreen_tri = function() { var submesh = m_geom.init_submesh("FULLSCREEN_TRI"); var va_frame = m_util.create_empty_va_frame(); va_frame["a_position"] = new Float32Array([0, 0, 1, 0, 0, 1]); submesh.va_frames[0] = va_frame; submesh.indices = new Uint32Array([0, 1, 2]); submesh.base_length = 3; return submesh; } exports.generate_fullscreen_quad = function() { var submesh = m_geom.init_submesh("FULLSCREEN_QUAD"); var va_frame = m_util.create_empty_va_frame(); va_frame["a_position"] = new Float32Array([-1, 1, 1, 1, -1, -1, 1, -1]); submesh.va_frames[0] = va_frame; submesh.indices = new Uint32Array([0, 2, 1, 1, 2, 3]); submesh.base_length = 4; return submesh; } exports.generate_billboard = function() { var submesh = m_geom.init_submesh("BILLBOARD"); var va_frame = m_util.create_empty_va_frame(); va_frame["a_bb_vertex"] = m_geom.gen_bb_vertices(1); submesh.va_frames[0] = va_frame; submesh.indices = new Uint32Array([0, 2, 1, 0, 3, 2]); submesh.base_length = 4; return submesh; } exports.generate_cube = function() { var submesh = m_geom.init_submesh("CUBEMAP_BOARD"); var va_frame = m_util.create_empty_va_frame(); va_frame["a_position"] = new Float32Array([-1, -1, -1, 1, 1, 1, 1, -1]); submesh.va_frames[0] = va_frame; submesh.indices = new Uint32Array([0, 2, 1, 0, 3, 2]); submesh.base_length = 4; return submesh; } /** * Return uv sphere submesh * * size - sphere radius */ exports.generate_uv_sphere = function(segments, rings, size, center, use_smooth, use_wireframe) { var submesh = m_geom.init_submesh("UV_SPHERE"); var x, y; var positions = []; var grid_positions = []; var indices = []; for (y = 0; y <= rings; y++) { for (x = 0; x <= segments; x++) { var u = x / segments; var v = y / rings; var xpos = -size * Math.cos(u * 2*Math.PI) * Math.sin(v * Math.PI); var ypos = size * Math.cos(v * Math.PI); var zpos = size * Math.sin(u * 2*Math.PI) * Math.sin(v * Math.PI); // clamp near-zero values to improve TBN smoothing quality if (use_smooth) { var edge = 0.00001; xpos = (Math.abs(xpos) < edge) ? 0 : xpos; ypos = (Math.abs(ypos) < edge) ? 0 : ypos; zpos = (Math.abs(zpos) < edge) ? 0 : zpos; } grid_positions.push(xpos + center[0], ypos + center[1], zpos + center[2]); } } var v_index = 0; for (y = 0; y < rings; y++) { for (x = 0; x < segments; x++) { var v1 = extract_vec3(grid_positions, (segments+1)*y + x + 1); var v2 = extract_vec3(grid_positions, (segments+1)*y + x); var v3 = extract_vec3(grid_positions, (segments+1)*(y + 1) + x); var v4 = extract_vec3(grid_positions, (segments+1)*(y + 1) + x + 1); // upper pole if (Math.abs(v1[1]) == (size + center[1])) { add_vec3_to_array(v1, positions); add_vec3_to_array(v3, positions); add_vec3_to_array(v4, positions); if (use_wireframe) indices.push(v_index, v_index+1, v_index+1, v_index+2, v_index+2, v_index); else indices.push(v_index, v_index+1, v_index+2); v_index += 3; // lower pole } else if (Math.abs(v3[1]) == (size + center[1])) { add_vec3_to_array(v1, positions); add_vec3_to_array(v2, positions); add_vec3_to_array(v3, positions); if (use_wireframe) indices.push(v_index, v_index+1, v_index+1, v_index+2, v_index+2, v_index); else indices.push(v_index, v_index+1, v_index+2); v_index += 3; } else { add_vec3_to_array(v1, positions); add_vec3_to_array(v2, positions); add_vec3_to_array(v3, positions); add_vec3_to_array(v4, positions); if (use_wireframe) { indices.push(v_index, v_index+1); indices.push(v_index+1, v_index+2); indices.push(v_index+2, v_index+3); indices.push(v_index+3, v_index); } else { indices.push(v_index, v_index+1, v_index+2); indices.push(v_index, v_index+2, v_index+3); } v_index += 4; } } } // construct submesh var va_frame = {}; va_frame["a_position"] = positions; if (use_wireframe) { va_frame["a_tbn_quat"] = []; } else { var shared_indices = m_geom.calc_shared_indices(indices, grid_positions, positions); var normals = m_geom.calc_normals(indices, positions, shared_indices); va_frame["a_tbn_quat"] = m_util.gen_tbn_quats(normals); } submesh.va_frames[0] = va_frame; submesh.indices = indices; submesh.base_length = positions.length/3; return submesh; } /** * Position in vectors, not values */ function extract_vec3(array, position) { var offset = position*3; var x = array[offset]; var y = array[offset+1]; var z = array[offset+2]; return [x,y,z]; } function add_vec3_to_array(vec, array) { array.push(vec[0], vec[1], vec[2]); } exports.generate_index = function(num) { var submesh = m_geom.init_submesh("INDEX"); var va_frame = m_util.create_empty_va_frame(); va_frame["a_index"] = new Float32Array(num * 3); var indices = new Uint16Array(num * 3); for (var i = 0; i < num * 3; i++) { va_frame["a_index"][i] = i; indices[i] = i; } submesh.va_frames[0] = va_frame; submesh.indices = indices; submesh.base_length = num * 3; return submesh; } }