Skip to content

Instantly share code, notes, and snippets.

@andreasplesch
Last active October 5, 2023 16:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save andreasplesch/817bc44089a7b7545149cf12acdd7c86 to your computer and use it in GitHub Desktop.
Save andreasplesch/817bc44089a7b7545149cf12acdd7c86 to your computer and use it in GitHub Desktop.
x3dom release with draco test
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<title>glTF Inline</title>
<script type='text/javascript' src='https://www.x3dom.org/release/x3dom-full.js'> </script>
<link rel='stylesheet' type='text/css' href='https://www.x3dom.org/release/x3dom.css'/>
</head>
<body>
<p>Window with transparency?</p>
<x3d>
<scene>
<Viewpoint position="0.91296 7.06434 26.89576" centerOfRotation="0.91296 7.06434 3.16048"></Viewpoint>
<inline url='"https://raw.githubusercontent.com/andreasplesch/Library/20782db654ce04a3014a519bfd8a180278bbe9d3/Examples/gltf2/Crusader%20Shield%20Damara.glb"'>
</inline>
</scene>
</x3d>
</body>
</html>
<X3D>
<Scene>
<PointLight location='0 0 10' intensity='5'/>
<Viewpoint position="0 0 20" centerOfRotation="0.5 0.5 0.5"/>
<Inline url='"https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/master/2.0/TextureEncodingTest/glTF/TextureEncodingTest.gltf"'/>
</Scene>
</X3D>
This file has been truncated, but you can view the full file.
/**
* X3DOM 1.8.4-dev
* Build : 1
* Revision: 3dc2c94be2be4c15746c9db43c90f5606a8cc486
* Date: Thu Oct 5 10:52:22 2023 -0400
*/
/**
* X3DOM JavaScript Library
* http://www.x3dom.org
*
* (C)2009 Fraunhofer IGD, Darmstadt, Germany
* Dual licensed under the MIT and GPL
*
* Based on code originally provided by
* Philip Taylor: http://philip.html5.org
*/
/**
* The Namespace container for x3dom objects.
* @namespace x3dom
*/
var x3dom = {
canvases : [],
x3dNS : "http://www.web3d.org/specifications/x3d-namespace",
x3dextNS : "http://philip.html5.org/x3d/ext",
xsltNS : "http://www.w3.org/1999/XSL/x3dom.Transform",
xhtmlNS : "http://www.w3.org/1999/xhtml"
};
x3dom.about = {
version : "1.8.4-dev",
build : "1",
revision : "3dc2c94be2be4c15746c9db43c90f5606a8cc486",
date : "Thu Oct 5 10:52:22 2023 -0400"
};
/**
* The x3dom.nodeTypes namespace.
* @namespace x3dom.nodeTypes
*/
x3dom.nodeTypes = {};
/**
* The x3dom.nodeTypesLC namespace. Stores nodetypes in lowercase
* @namespace x3dom.nodeTypesLC
*/
x3dom.nodeTypesLC = {};
/**
* The x3dom.components namespace.
* @namespace x3dom.components
*/
x3dom.components = {};
/**
* Cache for primitive nodes (Box, Sphere, etc.)
*/
x3dom.geoCache = [];
/**
* Global indices for the vertex/index buffers
*/
x3dom.BUFFER_IDX =
{
INDEX : 0,
POSITION : 1,
NORMAL : 2,
TEXCOORD : 3,
TEXCOORD_0 : 3,
COLOR : 4,
COLOR_0 : 4,
ID : 5,
TANGENT : 6,
BITANGENT : 7,
TEXCOORD_1 : 8
};
x3dom.BRDF_LUT = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAIAAADTED8xAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAAMAdJREFUeNrsXduWIruOlGjOzP8/zdceNA+QmbZ19yVJKFh79a6ChKqCkBQRkm38v/8QABBA++/zf+JD2r8k35/4lqIXd1zA/yj7i/ij7rP67jy+psRP77vszG87Lph4T3PnHYEIAAGe/x43BCAoH9L+bZ4i/jwMfotAAEjqxfx+7QL5pvyGAP6vHbxSfFb8TuPXDl6fe0O6bvYH+lm3OwDxP+b1L1ZYtN+O4ykAREJ45MKA5Efj8aB+UcTYCKTc546C3gzd6ZhLfVjfdLvjRj1Q/Ds3uPjpX/m0moujb7SZ8zrTfwBP9iu7d/b9PtFXiBXY8Ye+6eaW/IMCyRndKwUqO5KekgsDPCTBtPSfp0PjibwbakJgZCjc75aoAOBmd6yIjV8E2FN6wkD6vAfTf/X0us7YL5hK/30EabqM+Zo8Pfd1ysteGkAEdIt1swTLgcG4UDoMMMrXc+nffM86IuHMm1uQfyGRqQBIouKUEx7KesBiR5hzdWxNnAN3UDzob3b8p0RKQfeLrJC5XxAS4ueWveyO8AKXQVcqrOspUw2MsKtja+Jp7F/SG81veBqkOl+ECaS/kK0X2qANw7GKAABKesCRyJKrk9XEWfsyl85jkFpI7rOv6eHuL/g8HaS/ubNygUqGYxUB6SVdXqSx+WAYpOhQVteKxS0eCRFet4TefEjujfyaEeoyAvewC1TD1BLHUrSArSX0LO6GgUuHhtJ/GaUgx/ZFbsHu+9/RuINFoHKBIvalKB/99O+J2mD55pR9PP2DWWemOKFrQ+hKemB1VI5zHqECNIiJ6IHyymZ6x30WgN+I9dN211RPoqtlomoEaktgem4d6Phps1jQINz5nS0FEh1P3yE1ZbE8DTE+8DNgj4ZgnfycO1L+YPUImrmfGxIj7mfwzpYCiY5nX5vMLgKaNZRj7fsVU9N/pJEXB/ofmbqZImqny1w3BhQKVDqe0nyy2yazGVHjO6VYUMeL9KT/OkovzX8WFIHmld6rtEcaXu7Tj3Foi7dguk0WGhbSraF0WQh3ynoiIdAmO0f++q92JVNoYhFYFwN3ROIDYeJcWkO4I1c66V+hsClCb9SBCenffAuz4D5pyP6UGOhmL1erA3cEEr1/UJpfEZnrzlbYTCbO5u06MCf91y++qBM8ORjyYHSf0YfvuUogHgMQt0FfFKhe+KIGA/p6wPBSfSibxk4oNsw6MLq2K7kk5a+tR5lo7AzGgHEn8DXBoLSBNTIQbZMxWRxcRSDO6CcaXoGJhr61XWVUD/KfKxqTa2rCXLtzPAYsFwikHCy3ycB3SJufxhFg1IG4uanWAVDdm9GVK4Hx6bVKdwaET2tmnW/sBOnQ8/6jDwDS5KbjkJI/xalxodDq9QAdsrvF9sxF/+B+QQXH+c/C4rBYmZ7Qqc3GACTvF/oA4K1isWWxyoUgP8gZpkNqSIy5+FZUrNlh5Qtcy7md2j5YazHDrxf6AM38fYcsttwhmrBkMWIQiXVgSvofzK9n7zjyjs7AxE7t3FLAr69EMOj4TsliSxjEhswm1gGAznnJ0KCOwoWm7AQxLTwQgE71MVNRMSUGIM+IYG+EacxE4+IRWawVAU5LosMO4O/gYN2pW0OjwPUk+yX4j4SdiVAeIUJTRG1HeMCxHkBKsVoODspiZxJOn97p3sFBxHGbrcGSBCNffwYdOlfprmMy2fuNhzYKtBn2YEytgWqq2Du6qePQ5ghQig7FHdK+9e8hKAeI1jshHgPyCT4m5lP4xJQP9aP359cYyMoa/mzZ4PSAPYUanJiIe/waUueQoiuNjgr4YHe9y8ecldpTWBefWItgPStr7EWbbLM3eRYlQYIOJeuA28zqWIAyzoWm859Q7zbw6w16OOendluFORpA6AN4qtddzKUNURvEye55RWRxpA5oBk7WCApyoSuq4V65ssLZnMvmIykfon0AXfVCYfvY7qfdA6b6LylfMCQG2Pva0+tFNYRGqfyw+270vD/Cs4dwP2uEzXeTHygpENW/BG6ywO/doi+LxXk4FaAYGuDh6bZ/B8LAUEPnQ2vU9hwX30N9Kn7Hzc1uQPdlfWaD5l2XlCzevwmN9Gxdm4TFqdSBxL77kBCviYcCA+FvY0fF77aoHdvn5Xez+b5IuCMS2JkeR2UxxVwmg5n4bk+YzGR7um/hQleeHTpBuXaHQZb/FDboxoSCxwoZF1jUJbyRSdm0iro9Sjc68bW5gWk2GAa3spvMecR7uopAx/3TmU+Q3kQuKGzQ7eMX9EDhoGFggNmhTBD1fLR5ZutZNfMebOhO4y3DJ3y9awExnihbB1N+POu3LhAERm6ie1HpFwQbuoI7lPE6I32uoInZf8hf+LSbcwjPyQ3akYdGMnqqLFQuEBRpHlyyHpHFEHWHBMTH2Lnb59LqQKgUTG3oTvRG36KGu2NgJN+PpPzgNSA2wg62ow/qaIXCjZNIPVHZOYQvDidyI4pse3TkBFjIdN86wq+nOzsWAyt8GwgtjM2VBfHKdkmk6EiqjQJ3fs51h2pkOwDN9q3MiQxfznad++2zrNjpyydQ/WxB6iM2S12dbMrn8dCuB4AU6XedzYA7xPU3/01AmoQL1QGQV8MEywJ46547w8NrYL9BFgeKwAiDn+jqDKZ8QQTjpgNaIrTLUMO3Maeag+5QDtkd8wtdkgC8tT4d1eAt3mi0CIRjYApveVfKB74ksoSp2AoINQp08wdMd6iJJaBY0ypr+SclgVsKAIZ3ibuGJu4QuIOcZ6LY7Uj5zfX1KMSW7CFAVHih2OErCwOvA9AkeJECgb60JeLtGHwm62N2SNXIFkNLLVEf6/UVgyjHDBxn5XvQd6mRKZAwDKcAfVajwF3hDjodAqVL4EcLqwOQQn+NiAj6ozs6Xr4OwIwwWEdp4qOs4vsvu0AgAX1KowCCC4JrxgVd+zKodSO2WEfGcYwLnTbMPAvoYA6K4hitT0VC6spsvhf6AE8vU/Ze9F5VX6MAUguCixlSyHQJQkS/q907iwulBPGZm5DaMQAzwqAvEqbke1AXxSsuEBRADzYK0iYpKZpboUOQlLkW1Ylt7WhAlpSKHFmnLw5On1MlzlG3Hcm7L99nWxmCDQqKCyRyHrtRAMr8HOR3ebDpUFYWy4ERntcHs1WcRf+VTSEILB2eGwaD4LYPQ3Fv9SyQ5AKJnCfbKNA6yhpfgjAdgkG7k0WX+Fwwd7nLoj9+HtlbigDEls9Pd2+WPl0NgGoaVLdW7OVa4Gpfl+1IezC2+EZrrgH0OgBuqyusiUGa7SE9geF0bJ5YB2aZmCegufs9O2xQzQVqc7bRL9uWEUNg7EcIA4lNAbvGlcVQV4yovwl+KZDvUWZ7Utumu8elTVS6iUArxr0m+jaD1k13sie5AiCVxAbqtgiS4r4XcGlrhdFGMAsFKLNx7fsbk8XcITW4U7e9o3EhGATuQB1YsatPH7JpHoinFEVUK4DE77VSYHAeCM76m+6QyMEEkgPt0IRdMaIUX6E0vuHj7YvYERWX2lV3UNTSAuxOiZNiFMIOAz08IqI51UbgvlOcDgmMSK8DcjAwSkOKYce5ENC80/LOOu0UkjugTMnutPLPSQdA9Yaj0xAAj/NAeI8TS1KD3LI16JBtj4p1AAIjD5By+qXZHujeYaU3BiYO+msP4xiUp2uA7pu8JBJADwPR9VeYkmaDNu4QgKOMwaRDXL/KoQKW6w9MPwCFEn+HhE2UhcsYo9fhOdmbXfaPUYjWCyrIX/WQDmjOlFyHxx5BBWl/RZmd6znbdv2tHB+z+UFZkQxeQ75jd62lgLaSenhr9Sskdfe3asehgdEbkAb0K5sIQyUiKprdRWF1UZK1AcvZsjI2M3QLTXPkwRK1+nLK8VR3wi4SS42XS4VHQYFQxro6KRS4v3vIVAwDkQ5xWdxRB4xSAOGRB9FQSkFfveasImBxmwVk7Arjse2+QAB6GIiTQub92SHTRvi60zsgcf2OOuAkeO9igwsZfeJcXu89oX4FUi+4t8tQBRDYvxgGhV42+mIgrRGzPaVoUDFxAp4stug+ayb00XqH0MdOIQilw/x205OztvTOXz8e0KkASKWBI4pgw5apwoPd7w6ZWoJBCipg202DJ4st6RzI7uUfyD9tiihdc0+4HBPII/oEG+n6Z4D7FIhPfZRsnmNdS/kQGJ2whQQq3o5gt0lNAOjg+kp2d1kNxJu+kh6As1a6zIwB/B4ChC0Fqr38yvBRGJEA98DoRERIiN6OAXFQDOko148JVjHwohk9owcs9L9DEH8ElPsrQLM5TJwRlaMQEB6d6AkPxQN1ZbFooaZiybBHU2SGzMjJKYT3xsCJvblTKBBbEwwMwSIjErEL5UhF12SRWyVkh86FuDfdAMoBTWBakxEyY3ij/eL4M+sAXu/n3kuG0WR30LpjXARr2K0XDYM7cWSEB2v3gpSVyXDuItMNYYoPXkZXX8SDYVQh9MbALOvmUtq3+5epG2F6GDSsQywFMna1UpCcOAL2asIHiepDYh3Q4gQlgMhPQfXK7glefB9iousbzy0luPI1722bT+f6bWoUw4AFjI1py2wFVf5SvYslSIxfpfJ6HQBlvt/5GFBwDty339Abb+Q0b0/qePqr3Xe0IqPUwDZPdRmRSGPAG52wTFJtdk2iQ6D3blN1wAiVSOUJMXucN21Db4IqfRjWfQ0ghkGEEQEwYeBJiJCpKr24C1CxlAmPgjzkA6Zm7WP2dAEi8UcyevYVBA0QCQMkwY4US0F8pMI1SWUuxKqAQWNA2Y4KTLeHKPYZKGXHpU/fVATwMnAPPp1tiyJuEs/XBogJXsrWqtgVA8Y0SdVFwEqfNZLpQd/TAerqFFKxqMoP+MoYmI17PPGJ2GiAhr0MMaLgZJEYMLpJymU3KEfeAlfGpL9fbqHQJUeHyxmBL83OptfE/flw79EAPYyo7hCDO1lEEJ1IBd2qr8Op/ePRcjZDete8pjsGtI3505/9+iIwvlDhNMTHr69mgQTzR7szyIjEySKd/YOyAobne9TYtrEUK1YHLAYf7mHFkYHvg+O7dOeKJ3ZHiDALpFGd6k7tMuptIOiKWagPEtumiDhz6wB4aR7lImNH1FIev4gpnQ99fEeE3J+5lHOYHQokKTzSLlOeKIcBObC2oqgOJBd8RnjYJF4g/bhyXxsaQ9v59vyYF7QO9DkXSJSzArEJJ34xqCKuEXhzFqIkEJBalxLUH7KDZGgWbYYg/ghVmno6rnnlvti4N+ILuY7ksWEm/khgGK4RGNsBoWUBYZaxhCVBUFGsSJA9KDyXSi1i6njWC975/r8o2imS1SMi3uFIumsEZoOZ6wdgQ3LiB49d5z7bEM/xjbwYGDxKHrtWTvZfH/hx+A7ch88HkGZksYbaolJgcyTQesBaB5fd739CykNRiC+LARielsPPuRjPfamKAlUcgfFmIbPybB2RCvHAIIvh8KIBwbm0rjpgPErBEJpnCmES/vEysgL9sy5bHRuv8wFI0YjqyA1XCxhzjcizfQINOJv9U8fZ5/kY6HE8OwYlrsSkz7zstPh5USBN+1YI5jkJWQyES4EYXSiBOH5nxZFAn1ugOTEw2cHMvGa8IY0r02fHz5oCfZz3FxXbo3PSz7M4ClIBQA4DUjIfSRyX+CwA+31U9m84oSms98ZAQhMHTl8cun4lVlZfc86vio4LJLEdTenailksBRqtiiNeZv/a1Kc2Y4Nn14HUMoBpJCeQmN8Cu9QFS3+BdlE8mWEgWkCVYg4/RaRVTQeA6zltfI0M4ThQB1bN24QVKgZ+4mAMDGLXDexpr7/m6ffm067EQFD7lpgWn0KybaIpbI1WtYgXkYqyNSSrWCMGIKqYO92ejCDGwRiAVQGwFJ3nVJW75L3XH3+M8FiY1sWxpgFA3ASFI17XLRjEtJG3hl2jifbiGzkGvgPfpz16Vw8B9MLAuEB2jXRxDGx1vAZloVyANMmcgXuqVYzJGLA+mNSUxAibH2BBK9L/W2IV1D4AL+cBlKNGu5sLXEYk8h9TaYCijIWkK82EynDvA7qd6QcEMa5RtHhiAJyM/u63JUCBlDA4crl4vRckbRiQqXpR4DntHlgBZONAHYijPG74YJd27Pmwe4sAnhVO57/m86G7uq4kS4p6gwR0vlSpXuUVWmSDpRziOsGlK0GUR+bSMN7wCqiLuQ/hKWF2fuIHcVG8pl9HwsCylZo+cRDi5HF9/jGkaE/WMnLf65gmjkKtOwZMdK6+/7TIzL5m1QeglAK2SY5N63UHCVC1WW18O75nvA5497v1oZuiZD/v1Rn9veg/RwofIphcFsS+pdml4AgYCMWMJgnUGAA/MNDGdLZudJlCGJbOWYqC9IYA6MDraYzoDkg0zPUhjGwycKxLYVDazG5UhJxQPd9jRp5i19KC8ZS8Ltn3vHJyBRxOwnH3s+4UFMFj6b+KgdiVgir1QE+uYtE+JG0BMYbvjPDs/DgdxnCGJwbACbzoTL50lzaw9b+lJO45lNVS4KZ2jArlbHHA2J0YM4U6SUg8BvrAmilr41Fx2YBRbNBAGFAv7kdLgf1tng7ZHTEMeKYY2H1oIT8JhMoKWL9LLUx3iiQbVDtUmmK4B+lgo1mlIGZMWfjurQPoiVEMUOFoidAKUW8dwKmvv45uwbldZxRsUAYvCoI7kv7NUjCU++PfBu0j3Wm1gZ6Ao2v4hNcZI00IiSvXikWmEHIbFNwuWDAMhkMC5oYEpCsDmtICTbCG6kMvTCNXdpaFCwfAUlPoTkhZ/ycdBjbWx7SE8S2GK0Mw4xpYR685gFkGrxhKCSkyzII6YZ1vOb9xVPsurTPXv+3I5YPhEX+oXBIZsJKMxhmaHqiB9RxHChCYWRl9YhWa/tz3UqM7pCoA6lIhgG+0wyOIewPHtkNqz2sEFILBhbAXfNiVSkP65EQW9JYAmBIbVgUgCOds9zIWKjjGlJBmgD48aGQQGLSjxQZojOdEwJR+kQB3mh4Apxmj8ddMaoA+vEpflycHp8oCBioGdtmjGKgMGCgOCJmiYWtl3TON1IrxsnCOhp4eG10uUFwDzPua3FLA5+TM3I9JphS6XpO/ItwzBCkE0GAMTCkLU6MC3jpkColG2HgF6Pi6KAUYKx2YLwsaU0J3JC7AhXJw1wiSRqgmCQbrnoB4WKR63+4RHQFw7Aw6owKgB/fj63K8IvBERHN8mgJ8RqkDGNAPOfmriwE1BmyUhyPkVwSyj97103d7sjvxkIiVAtLojac6gkDnayblgQWvUKASFWJlmBIbg9/CR/XO4JTFBtDaoPYs0BjJqfa+FbN7ETzo1g27LMSArj4rzIWQHCKk3hmuGwZNMlhWUC1MCZLviIE76bNAEbijwVsUGxRRDRhq8GrGA9rx0EBKgnWU6ihkSQSoCM34lVPU82nU6OTAWMGF6vUABhfiX7MEf0hSsywQVwt1kGBANkRCEc3wQH0gz54dMICCMcKj1gQ7NgZoknqPubDmy2JAvEDZFiXChQxzU8SlpBYSBSTiCJGX+1kdwGDF4PhT2gVoWqXBwOivA33TqZesA7BgdIJfwxphWRkgGjhSiUBFIWDszlQ8oEFyFKvHAJOxHiAkBlLFQUHwirGL1YIY3jcmHS8FhQh2+Y/B7EUzVCkLzaNiKaBGKrh0SMn9/tecC5HOfCIWEI+BmBoO1QFPP9gPrSsLl5W5vg1KcRu0uB9NUlQd/qU8isGsH0z/ZUfMqwOoyNwITdLcGEf7Fi/iB4bImuJfe+vlL7uqZrXlb4pgrnTBGWEAN/HXA3Aq4pWsT8R2gsBMly1TBxLhYRcQE8GpL1wTaRYv6sb3BUegOx5VOsH619id+HdNrJcCkp7YxoMOd2x+AVJNTxmpnlQYigFPDXeaql8aAzDc4cosiMkIX1IkASqJv6ElxEhLUwpKoMtKQOs089Re21BIFtDtzlRq1CdI8UckgdtTy64Q6GYvOCOvvysMChcoXAQQ5QtKUoQ1ESL2RKr5ukp+eAwkdQKyIBRJP2rxoF3m6VH5gqQSUCNKm7NY7xHBslloWDP67z66uUDVMSxWJMjsv/FwpLIghgGWiZ+TH1ZVUIM+7wx42gB1XiTA1GQ+cUfIevFBbXAiFxpc1w/L+lxdGsB2gQJsp0R8Ew+k3IMSI9ISP6HUTdPTPzIZYNQBzMaDIRvM3BxsGI9A/4IxcD4p6uoDuC6QXQS4umWh0ihgoso12ktBSABQJtOLEI9gPR4DWQM07hHF9In7c8+MAbjMqHNGA2RcoNYO4kWgRnYbBnaaV3hRhCC17N+2RPXc79YEx7bPGzgYiIpxXmQ9NHV98MlhEIyEGAXS0Y+iwa8UAarv2SsGsqgoS8FOdQzO416DXh2wuZCGxT5eZFuiMDIx0SsDpi8tOC0MphQEkwKZuZ+UvOsznzLl11Hxem5RCqgMhjoqIEWHlDrQjgxpuR+8YMjLWf/O7MTEjLHqifbopbJ+PBgyNqgJ+mDKbzI9NCSnDIb9M64viEAfxfQvwl3M/Z4Y8OFolog4U5rfIsiHBLx7hHMW1vUTYjwb1KD7otgFOjL6/i2x+5ErYAnrlftZl45UQVA3bVZyvy+IPfY/x9Lp9oimbuAF15tkHmRBCQ3QRkj5I5UuGEkXvI5I0kh/A26G9ZYOeZUBdVKEujAQxYAtlDGia/UpUZfxdwTVoi7BpUweHEj8jALZGoBdcOhdVijEIqDleFKCobm/GpdQKoMtA9wOGnr8R1YCnjCYZefbsuH8zgCcOPYzy/GURTCJfQAT9AL1B8n8aaKiJkL7o7SphQbllQaoE7/cEi5/pUAwIKkQ56YnatESEAaWbPW6BJCZfZjmir6JAk3HeuSyYk2wSISK+0kKjCrrb5xn/xZLoxMq9IulgOpHoagbtL0yRrDeZRnZBSEaA0bHoIvApJjMIAWCc+c9p3P6juCpKVCA/GhZ//BzoBC+LM2/mL0eDMe/hTawwB2IAXTTv14QUM/xIWpku0NroJxaYAnTpyFobWrHqSGxdYJN8iM6oVRXDBTdT2hJDtSJvMzuFcOpU75QH2IxgLFGgRMVGqAjitlN/4E2mQXlgTXEbxmIWIf1vp9rukD68wnaakAFKZKLAFToL1VBBXTOcCS4t7JYa4FJvhBqsxKxyiAHhlEHYqNE012dJUcWTBUDE+38/grwOineSP8c+vVDIuk/agIrAkcpAAH9PNmLVaKZIHK5kGyJSpDVugSi44kdyvhEZ/PkLRZP6FtNf+VKA4h+P2rdADHfVz6KUATEUnDgHiSqwwSx4IfqMYCiHoAQERLfOEMNq9XAtINsd+hquJ+e/nENyoPPkkQwqwTG65LIguocDzWsIY77/TIQ7pRVr9QxEPSAQoRsMeCr4aQd1NHZjcP3XbjHE7HeFw/l05U+gKEEGsSz65scDzWNYXAV2E7b/4Kj+cC1csggAkseoDQogZrbYw7SYdgOmrX4azXQ32uDToQ7dlYAqSHAXSCUtEETD1D7ntq/UJcFC/di/0siP/LaAGVWFO1qIBEVNFXBiDIOxUD+pNR1hwZo21MvyuuDFSBEgbJxVpmepSBuFC0I/0JdFoAZpvIzQbkTVCmMPP2DUA2y9ija1SOT/kV/8/zsvm5JwDlwR0bmcTwAkI0AkXmlcBwRV8BlB0BHv3oPKCHFq4Fcm/SQsP91G2crm8R9kF19cv24Elhh/2vXJAPAaBIzlq8GA1VKtG0LBNBv1o465eslQqsGnLVDV8PYrQPckgo2iadg+swDjt7ojc4OADApfo1CMc0TquSlVRw687Hs/xLTjdgFtRqgdiJGpBpkGFEHKQraoxMhPuUEu3NGncefOFkDaOlfrgY17QFkU9OlL+XWAZF4iaFSuorKrI5q1dtKQGkCRFjQii1PBvGdzegng36EKZFgg07FfQVCVgRAckXLqekG6JUfWpQaUpK9JkSw6QlA5YoiWcwkogpQ7zH3qOE1O8AtpDc0H/HjTr99fxEAkdSdvTXmKecsrHEGRRsB6soAzbAQqNRfJj/1L4Hkn6fdOJXojQxB2BcCZTVmqhQMiuNu4n6FWf9ZT1QqgGKcD0K/6QaU6wQEuBeVQaT+7WZB5MkAcYfnRisXuDTaAoO+kLHFUDYexhnRuodWMJ+5BYHaAHDhXp7l2A195qWWd4IYCSKb9ywgLR5ki10KEnT3CLJ9oUk9gaEwkNpkI7g8DfHjTa7IbyJUAOr+VaQmlwx9adkASN8CG4iIjANFY0DUwWCKYIjqgQ4vqD8e8vuhT4Tj3M06z+Q/LQUihfyQMh0J/vIBOeu3W+7WA9VQr7hHluxbI8htArgpsxAGqi9k20RZ9n9KKYA1s/vv4vrjNUGqALfXmmDyaA/EhuWcHykuHthfTdpXq0n8AE4TQF4GILmiSObyK00EKztBOCyoY2MVTyfA7O7YEDWnyfCdDnfLBSL3pK0I7qWMq27hXIhgkNYWNz+SlJCwNADIygEz8cBlq7tXSjD3h2akxw7Me72fM1z8cwTr+WXhTvuKsJgCPlZjZd+OhudIewo1/ma743QThYGBUM3xbBCj7scWWMgiGjvo1Q0V9L3by8Hipth0OJ5sd6oB8GAuEFcC2OiBOhgaHBpVoZmaRk15K0ctubsAWatkvHGJ0hL1DzKS2sBGZ2CWGIAZKwRg8VLGCz59gggGa7fc9F9C7IVC2/8HggH1BTHI6BAGTqV2UzIGUinapuqy4dCoDFi8kckb4R7YFsUVwdsrESlekLJ8lvTHQ22E3mDQlkTyIQjREhWYj0HKk/a/saoYxoaCYFJHrLMO0CVSe9fmuE8NYBzAKPEfLRhIX05OQbjrl2E4GHA3NGGoFJSEB/XTwZxI0PjSAP+B2c3gcTsIAzltqZ8zRwOQ6P9gfZiFJEFdDV0x/noNcSgqlMMn7WBAUJcEIFscg8rYXBCymgzotoMGhe/S/XzeOL8w96XudCMojsjQ0j8V+YwYdGjgHZFX1pN6RB9456XyL7DJ+nVgJzZX215E7A+ELFGvN3w2+59SCugSubzvBe+EdPSepDqAzPARcK9syBN6Z0rjv+wBl5ke2AllYpDY8bCnc0btsLlG+dqpA4FRuRW5f8oGJ7Ms0aXdqxW/Q9UHIPAsoHrzWpEUdfyKpN1fdgCUQ4udOxW3FJvQbRhR7HAKdRqCb69i7kWXmgmdboAuZf/X5P21BmhcIIUxU5EFX3cbW88y28SumdU6Hdb2OpYKkH+Id4QRlcUNFakDsaOtnd1Cpe6bhtEhCkRv6IKt6GTR6RHVukAEVieVH1pBKB0wEFtFgGKmB2UCFHS4d4dBaQoVvxWC5IrGuVBwJmLgnMmZdWBwGdfUEaBzoI+iC0QgEQaQFbDQ/bWPbpd+A95fg2IqjgZCAmHjOSh8YW2SlTy8ETNtAQg7nlMWhZ3UAbieEkhqgBsd3KNsBjNP0DikWqBD4u9Fer5uVoGJoFfDpYW+XQ2wzPpiN0BfjKvOBZkQH6RAsHICYhb0161foZVxdYhg4ohhAqDsc1mHtkvpH9kf0wKVbwax/0QonB+OeyhmhzKkCMVugNT/EtdPinyGd0VmzcCNoH/F9lUjoLxOPXmJ4KMCSJOV5cnV1eFFcfTv1qqOSQB5QxRBGUn3oBFkniQQw0DMvsi+iAsAF53zBQBN9fsHQHkFe3SsAkC9v+cWDx3odxxLFgNUDmyhWRpJFhlcEkTCAClGPML7BkzZJhrm9r9oDPr0kaDvrQD8+Lr6yF4EuD2f5aJ/DyotBkA/tgyU9M8iAUtJAHIrANmHhqSuCoj2AcwD9rJFoC8SYPZiSHxfsndt0/kVgGriW1WAmvY8q8GOvwfBra4DGvqNNS0gCYBgJBzA1QsCsAPi0dwgqFLGxo5DpthN7Qk3vfv7ESJ4nTwg/Sn3x+04I4zENbgS/9nZf6X5Gh+JJKJRGKkAiqNa62BoRu7YH4f1H9dm/ZgqQN4QkHx6DYuR5le2CPSZPx/n/5zAlKwjkp59gHYagg1Ocv5Tsf8CVQ+CG7WRR1C8TmA7HzWUiZ3XYgYDb3ghSDaozumD/gzE1DCYQ/+QCYl16I/inq6O+2gfgEqoFL5n0wFoKgCHflUEOO4pAXShFAQ5IBPEuIkBnvux+K8xLuXVYSYK4woy2wKbjv4LDkFcQgST2QEopyl36r9n01vTOd5wv08ZNf9i7F8Z6KhT5GaoAXV3SLSA2HmSNkCNVoCxK1a3+zlk+AxD/5tw34rgfQ6CmiEzKvZzplr17imfAAAehQVU4j6+l2Ekr6Mu/rkSEOSP+K2yHgBA2DNUHjuLnK43y/+hEPrFlb74g75YAY7cTK0MePmVBfkpK8CtJj9UU38j5avC13Z+gjeSHBg4rEwsOwOSUYN6SJTeaLN6OCUos7Ab9EZwAJ34ybhHzzA9KBDVew9Cc6xvLX9x07soSt69b1D8SwT4sGb1y33PSVsqmZKJxfoy1JZuspUAwgvqpifGFHCHBkjEEvVEmruXm7vj5WWhj+FIwMoFwkIHw5bR6xWuSC3zAYAHwo0AaPN/CqunSf90AySgh2L8o2KGdg9DkRQPCilCtJDkQjaS/iO+p5v+xYWawfiMw/SzJh2y5ZGqrRFvVCKVk5Vj9qFI/4fwpWpLFQIF+iXibwAPdicox+9J9xsGnLzlGypKWiwpmg7W/VC7CMQ/rXOOAsCvgP6s0/JaDXD0gwvyo6X/XfiWuZ+o4j8786kQfwN8tGmekA3/xJZOIqf+qKrqtg8Agg7ua8TaB7BiBvQ59NNfGfpfMg59BABTwK9ca6b/ffaBisCBWuk+mQ80YYB1SHDIRsY/NW9USu0I7cQoApuDYEemanUgWARScO+oA7Nyf3ab148GfR0Az10hip1riapzTp8BYKR/ourf44tHRYGarF9+y8HtLHwBITz8T1cnQuJgXGrUDDN2ZFxazEK/7SjgZeB4/g/aNEChgGnDRGuAbmtkm/T/ALgpiX+nQLAxn1IB70vAqlUvbJcUeRy6secjEHE3dYzr4LD7GUHtRPSvoz34RaAXKFAlghsLqI6BGxQDP2LiL0C/5/4D98+IaWwfEj58kvYLculQAiJKX0xsbEFgU1RV/ga2/oQxq74bQHgBXL7XQaoDgB8wuslZ3KjMYwuDp/v5jIQjAB4F6IvcX+KeNvZ/8H4Oeopt/WB4vCYVRpQaYfyJ3vEtEc6d9UBHZh84w5k/9kPfgHs9AGo9i4UGaLUvVbSHdpJTCIDXt4+6DuzBUKOAwNz0E6qFv4gSwzb7fihOB4G0HqAbhZjrLqVG3+ZQ/3cz/qsNRygBIPGfZ8d3b3tRnf5fvmdRAYA2oCuIL++vOsGBMBDn21Qb1FYCiokZFcHF2GzfmuAV9uXqY/C+APosAJomAFS5/yA/JJCfw/O5bU+vs/6L9jTf7l9gbVDWYYB8U2iOe7TqPrqfTOGKGppY7Z3NRUByqymcDbU/An0pAEDuAb9Gx+jA/e78EFSknxMe2n2iB0uJpGZLKhcf2tWAUyDqQb8K+phXA/ktISa0wLr3+qResNI3gN4LADgWNJZF4EB/k/tJhv4O+j39H5HAw0Acj9aOgQFhe4lqjx3wmwMGZRdXw3SI4O5MGV/5fsHbp0C/DQBBAW8B8BK+qKO/lrzP6kCPNv2/ug0ohQEq9UE7+xFixzCR7gKBPAc6KEa75W+niTk1JeOfwX0RAEivDM0DoGH/oKN/4z9PuJcV4FkQKnqzh0ED+uAKseYQbN0eFXtkfKGMOMODxoowVHdjHpmEC5Gf3p1of+jXA+AfqwClBdSwf3yZP3uENOh/8Z993nPDghwGDfQj8QCsU8GA2zGqjjNy+Yh78/ZFJ/jHcK9SIGrYP7TaF4oi8HgAaui/HdVgJzzUMB9eCqiOByUGWn0sDbSlabq0MqZsEcR7BRpQUpNwTcrv3vF87uj/FyAeOwLgtSCY4AYy86Gd6hTo37P+qxRsib/aX5O2e6i6BylWBzRFa7qithpGvYwM+j/dLhCk0D+pOn0T6I2/ogoA4v4P1MIXZPTDrXgUigpQZv2dDpVwL6OCXo+iXgGQrFNc+XnuQDn0Z72Xjq1QDHsRp0JwhXb/Gty3AfAobdDS/4HD+jy6Yxv6oYyKnfBsoKfS7akjgeoi8LyH9uOHXRZk7MUb6wygtjLmAwGBp6GfPhvragA8mj5AwXwe2xe3Iv0/4FjtRQXRf/0LB/pf8dBk+oYFlRkd/cO6q7+5tINI5jNNGGhzmhh2Y1IsqE/+4rIIjDunf6TncAQA1R0ALCgQNfynVAIb7WkrAFTor4gQ1iexamGAlv+DeltAZeropLTgid88eKZYRhN5/AUHga6G+yMA/nsTbNDD/YSa/Ijsf0v8bQWQ8n17PzFe4p31qxEh9Gj9oMWJfwMcfwf6sgvUBABAJX8fzwGhh5L+66z/+oKFBP+iTf/SWUYGEYr0ht872bto6/Dph/7iX4K+LIKxCAAh/Re4hwL6O+F5wb3kFw9WChoOYhQE4/gZJSRQapD1W9146gJwW3uMDCB9qJlzwq99f/yjB1Ui+LD/odK+Le8vsv6L9O9hwNI8oQR3VOx8+2yvsJXZw4WGtzDp6wBMWQ18/tafXxBvmwimgwId9j9UCyMfexGAog7AK/2XfEYgOTX0CaVIiBSBshXQrETxNnaekoZX6Nd1q4FntoHpq3BvBgBL/4+S/2wN4z33U/0uEbWYfu27yENCfGMxWgQwaMgofS6dl01OddONoGvi7FOnQV8uUB0APP03FOiA1KNoY+E26gzbRkPifyIGRUaE8k5VhgZwmQ+OLaG61kbhlzmk8ZOnQbcKAEUA7HtFvdL/Rvpb86c5EpIEhlPxfqj9UPsdnaEEctZ+hgX1yYAsO4o8hH8PtesDoCgCuIvg3QDdSH+T41/9AU0MgLTvZrAOBNbmvjeHjaNw2iAQjVL5PxU/mwu07WkOVCzfZRYQfwePQToGYmKnqlMzEBH5UOw5H3v8gaL2zjoc4DeC5ptKx/2/N6IyALYYeDYHKu0rkh/aeL844CkO/XMZoFGgSKZ3y0X8czUm/s0jU0+60Q/9iygQHcsAsNy5sJl/Lpyfg+5vtKf5r6XDfEkIFh+o2xwA1eX8iE/3gjuJ4w/9WgDcSmLTCIDN+TncT2LpHyXLHyQZgJlFGbQeMvRX0DBZV3xZAOxZtu0BF6T/+RWh6XXicfhkdQ9oB7lkZEBAGaPHcK7I2mk5vn/mjxIAsG0DsW+NVUw9EBXof0L/Vq1lUQc/QaBDBOZJLPHEHH60xw+dVXZWps+f2T9NBEPNf16zbUXn60j8BfRLqnP8B4XN3/Af5b2Ux4SSo/mrP+lZczXRIQj6ExB8dwX4R49HvYE9vtB/+D8I9NzZakP/EQM3tqkJKto3u09sN9BHisZAhMxHpyf3f/EwiQJtyeYGQE/0YzHz8zh0MBToVxc6gh4D7NMj6BrH0U+m+FCViX9Mel40AJ5YxNIFeobB45jwKT0fOQb4WS/NF31rq5I7M+c2hjgFf3jJ2PvLAqAKAISaxO8bnOzmD722PxEGnkEy9Wv0O4MymMfbKSMSkWDAzwHNjzXJLtDrRIkbIEJ5XkZ1dliNftIt/wM2UrKvJiri++/QepRfGzQ/7K4JAKTHDQDghi9z81giTLXrT2arC1tLlDp2QMYzTJ5O9NEPlF9MgZ6ew+3Ys/a16HEvBXyJo05aqGFEwDgShDfbd9G/ZvE7nhB4P417HQqECHSDx22rA0X6LxO/trMVRVhwatVJqg8wDlb8jZr9UfZ1fzwbYQjwb6sA9QKx47SLjuF+7b0csYOW8Zkf7P6oBqDbqwLQv+KcvMdB/ZtdPl+mTb3bM/0+zOv/6T/SpVWA2z+gfwD/XvznmALiEz6QP98Hp6LpHcMRPxR+cwWAGzz+AT5j4Hl7yBPOO/pFh4c6ZMBbNxzs2BTkR0u+LQDoRgRwuwHdNw+UXjM/RNW+bny68yUTxI0eapFwwaznD1DQZcLvV0lWV4AX/2nO/tWnO334hB+ibvh80Tbfv9s7AwAR6A7077VrOT3UBS6q3p27QdTv9hMt51Gg5y64/14BANvYc+X5iFResjIp/2a+PRB+kfjnK8A/uN0B/lUbPRCf75+BF/zzSPzF2+UqAN5fCvg4JFjr7zaLevuO/7wAdP4mCn+xJwfAUwHv6wD2ESDgQ84LWOLPzPih+d0B8J9XB0CWvyjrV+r6WOgzEfZD21eL4DvAv4r/0C9bf+jt9wmlA+BG9FTAj3JTON30/CXD3+2rAuBOcA8MNv9uv9tXBgDcCe7F9ugS+SWXF//C5qe2P7UC/E+5Ibr1BtDv/f3dvrAC/C8dO538UPnL1n8wAMiF/q8F9SE3+r3BPRrgd/vd/m4A/CP475vOU/7dfrf3i+DXaXi/2+/2NyvAr3n4u/3pAMBfAPxuf/f2/wMAQAlQpLLpCe4AAAAASUVORK5CYII=";
/**
* Stores information about Browser and hardware capabilities
*/
x3dom.caps = { PLATFORM: navigator.platform, AGENT: navigator.userAgent, RENDERMODE: "HARDWARE" };
/**
* Registers the node defined by @p nodeDef.
* The node is registered with the given @p nodeTypeName and @p componentName.
*
* @param nodeTypeName the name of the node type (e.g. Material, Shape, ...)
* @param componentName the name of the component the node type belongs to
* @param nodeDef the definition of the node type
*/
x3dom.registerNodeType = function ( nodeTypeName, componentName, nodeDef )
{
// console.log("Registering nodetype [" + nodeTypeName + "] in component [" + componentName + "]");
if ( x3dom.components[ componentName ] === undefined )
{
x3dom.components[ componentName ] = {};
}
nodeDef._typeName = nodeTypeName;
nodeDef._compName = componentName;
x3dom.components[ componentName ][ nodeTypeName ] = nodeDef;
x3dom.nodeTypes[ nodeTypeName ] = nodeDef;
x3dom.nodeTypesLC[ nodeTypeName.toLowerCase() ] = nodeDef;
};
/**
* Test if node is registered X3D element
*
* @param node
*/
x3dom.isX3DElement = function ( node )
{
// x3dom.debug.logInfo("node=" + node + "node.nodeType=" + node.nodeType + ", node.localName=" + node.localName + ", ");
var name = ( node.nodeType === Node.ELEMENT_NODE && node.localName ) ? node.localName.toLowerCase() : null;
return ( name && ( x3dom.nodeTypes[ node.localName ] || x3dom.nodeTypesLC[ name ] ||
name == "x3d" || name == "websg" || name == "route" || name == "import" || name == "export" ) );
};
/**
* Function: x3dom.extend
*
* Returns a prototype object suitable for extending the given class
* _f_. Rather than constructing a new instance of _f_ to serve as
* the prototype (which unnecessarily runs the constructor on the created
* prototype object, potentially polluting it), an anonymous function is
* generated internally that shares the same prototype:
*
* @param f - Method f a constructor
* @returns A suitable prototype object
*
* @see Douglas Crockford's essay on <prototypical inheritance at http://javascript.crockford.com/prototypal.html>.
* @todo unify with defineClass, which does basically the same
*/
x3dom.extend = function ( f )
{
function G () {}
G.prototype = f.prototype || f;
return new G();
};
/**
* Function x3dom.getStyle
*
* Computes the value of the specified CSS property <tt>p</tt> on the
* specified element <tt>e</tt>.
*
* @param oElm - The element on which to compute the CSS property
* @param strCssRule - The name of the CSS property
*
* @eturn - The computed value of the CSS property
*/
x3dom.getStyle = function ( oElm, strCssRule )
{
var strValue = "";
var style = document.defaultView.getComputedStyle ? document.defaultView.getComputedStyle( oElm, null ) : null;
if ( style )
{
strValue = style.getPropertyValue( strCssRule );
}
else if ( oElm.currentStyle )
{
strCssRule = strCssRule.replace( /\-(\w)/g, function ( strMatch, p1 ) { return p1.toUpperCase(); } );
strValue = oElm.currentStyle[ strCssRule ];
}
return strValue;
};
/**
* Utility function for defining a new class.
*
* @param parent the parent class of the new class
* @param ctor the constructor of the new class
* @param methods an object literal containing the methods of the new class
*
* @returns the constructor function of the new class
*/
function defineClass ( parent, ctor, methods )
{
if ( parent )
{
function Inheritance () {}
Inheritance.prototype = parent.prototype;
ctor.prototype = new Inheritance();
ctor.prototype.constructor = ctor;
ctor.superClass = parent;
}
if ( methods )
{
for ( var m in methods )
{
ctor.prototype[ m ] = methods[ m ];
}
}
return ctor;
}
/**
* Utility function for testing a node type.
*
* @param object the object to test
* @param clazz the type of the class
* @returns true or false
*/
x3dom.isa = function ( object, clazz )
{
/*
if (!object || !object.constructor || object.constructor.superClass === undefined) {
return false;
}
if (object.constructor === clazz) {
return true;
}
function f(c) {
if (c === clazz) {
return true;
}
if (c.prototype && c.prototype.constructor && c.prototype.constructor.superClass) {
return f(c.prototype.constructor.superClass);
}
return false;
}
return f(object.constructor.superClass);
*/
return ( object instanceof clazz );
};
/**
* Get Global Helper
*
* @returns {*}
*/
x3dom.getGlobal = function ()
{
if ( typeof self !== "undefined" )
{
return self;
}
if ( typeof window !== "undefined" )
{
return window;
}
if ( typeof global !== "undefined" )
{
return global;
}
throw new Error( "unable to locate global object" );
};
/**
* Array to Object Helper
*/
function array_to_object ( a )
{
var o = {};
for ( var i = 0; i < a.length; i++ )
{
o[ a[ i ] ] = "";
}
return o;
}
/**
* Provides requestAnimationFrame in a cross browser way.
*
* @see https://cvs.khronos.org/svn/repos/registry/trunk/public/webgl/sdk/demos/common/webgl-utils.js
*/
window.requestAnimFrame = ( function ()
{
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function ( /* function FrameRequestCallback */ callback, /* DOMElement Element */ element )
{
window.setTimeout( callback, 16 );
};
} )();
/**
* Toggle full-screen mode
*/
x3dom.toggleFullScreen = function ()
{
if ( document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen )
{
if ( document.cancelFullScreen )
{
document.cancelFullScreen();
}
else if ( document.mozCancelFullScreen )
{
document.mozCancelFullScreen();
}
else if ( document.webkitCancelFullScreen )
{
document.webkitCancelFullScreen();
}
}
else
{
var docElem = document.documentElement;
if ( docElem.requestFullScreen )
{
docElem.requestFullScreen();
}
else if ( docElem.mozRequestFullScreen )
{
docElem.mozRequestFullScreen();
}
else if ( docElem.webkitRequestFullScreen )
{
docElem.webkitRequestFullScreen();
}
}
};
/**
* X3DOM JavaScript Library
* http://www.x3dom.org
*
* (C)2009 Fraunhofer IGD, Darmstadt, Germany
* Dual licensed under the MIT and GPL
*
* Based on code originally provided by
* Philip Taylor: http://philip.html5.org
*/
x3dom.debug = {
INFO : "INFO",
WARNING : "WARNING",
ERROR : "ERROR",
EXCEPTION : "EXCEPTION",
// determines whether debugging/logging is active. If set to "false"
// no debugging messages will be logged.
isActive : false,
// stores if firebug is available
isFirebugAvailable : false,
// stores if the x3dom.debug object is initialized already
isSetup : false,
// stores if x3dom.debug object is append already (Need for IE integration)
isAppend : false,
// stores the number of lines logged
numLinesLogged : 0,
// the maximum number of lines to log in order to prevent
// the browser to slow down
maxLinesToLog : 10000,
// the container div for the logging messages
logContainer : null,
/**
* @brief Setup the x3dom.debug object.
*
* Checks for firebug and creates the container div for the logging messages.
*/
setup : function ()
{
// If debugging is already setup simply return
if ( x3dom.debug.isSetup )
{
return;
}
// Check for firebug console
try
{
if ( window.console.firebug !== undefined )
{
x3dom.debug.isFirebugAvailable = true;
}
}
catch ( err )
{
x3dom.debug.isFirebugAvailable = false;
}
x3dom.debug.setupLogContainer();
// setup should be setup only once, thus store if we done that already
x3dom.debug.isSetup = true;
},
/**
* @brief Activates the log
*
* @param visible
*/
activate : function ( visible )
{
x3dom.debug.isActive = true;
x3dom.debug.logContainer.style.display = ( visible ) ? "block" : "none";
},
/**
* @brief Inserts a container div for the logging messages into the HTML page
*
*/
setupLogContainer : function ()
{
x3dom.debug.logContainer = document.createElement( "div" );
x3dom.debug.logContainer.setAttribute( "class", "x3dom-log" );
},
appendElement : function ( x3dElement )
{
x3dElement.appendChild( x3dom.debug.logContainer );
},
/**
* @brief Generic logging function which does all the work.
*
* @param msg the log message
* @param logType the type of the log message. One of INFO, WARNING, ERROR or EXCEPTION.
*/
doLog : function ( msg, logType )
{
// If logging is deactivated do nothing and simply return
if ( !x3dom.debug.isActive )
{
return;
}
// If we have reached the maximum number of logged lines output
// a warning message
if ( x3dom.debug.numLinesLogged === x3dom.debug.maxLinesToLog )
{
msg = "Maximum number of log lines (=" + x3dom.debug.maxLinesToLog +
") reached. Deactivating logging...";
}
// If the maximum number of log lines is exceeded do not log anything
// but simply return
if ( x3dom.debug.numLinesLogged > x3dom.debug.maxLinesToLog )
{
return;
}
// Output a log line to the HTML page
var node = document.createElement( "p" );
node.style.margin = 0;
switch ( logType )
{
case x3dom.debug.INFO:
node.style.color = "#00ff00";
break;
case x3dom.debug.WARNING:
node.style.color = "#cd853f";
break;
case x3dom.debug.ERROR:
node.style.color = "#ff4500";
break;
case x3dom.debug.EXCEPTION:
node.style.color = "#ffff00";
break;
default:
node.style.color = "#00ff00";
break;
}
// not sure if try/catch solves problem http://sourceforge.net/apps/trac/x3dom/ticket/52
// but due to no avail of ATI gfxcard can't test
try
{
node.innerHTML = logType + ": " + msg;
x3dom.debug.logContainer.insertBefore( node, x3dom.debug.logContainer.firstChild );
}
catch ( err )
{
if ( window.console.firebug !== undefined )
{
window.console.warn( msg );
}
}
// Use firebug's console if available
if ( x3dom.debug.isFirebugAvailable )
{
switch ( logType )
{
case x3dom.debug.INFO:
window.console.info( msg );
break;
case x3dom.debug.WARNING:
window.console.warn( msg );
break;
case x3dom.debug.ERROR:
window.console.error( msg );
break;
case x3dom.debug.EXCEPTION:
window.console.debug( msg );
break;
default:
break;
}
}
x3dom.debug.numLinesLogged++;
},
/**
* @brief Log an info message.
*
* @param msg
*/
logInfo : function ( msg )
{
x3dom.debug.doLog( msg, x3dom.debug.INFO );
},
/**
* @brief Log a warning message.
*
* @param msg
*/
logWarning : function ( msg )
{
x3dom.debug.doLog( msg, x3dom.debug.WARNING );
},
/**
* @brief Log an error message.
*
* @param msg
*/
logError : function ( msg )
{
x3dom.debug.doLog( msg, x3dom.debug.ERROR );
},
/**
* @brief Log an exception message.
*
* @param msg
*/
logException : function ( msg )
{
x3dom.debug.doLog( msg, x3dom.debug.EXCEPTION );
},
/**
* @brief Log an assertion.
*
* @param c
* @param msg
*/
assert : function ( c, msg )
{
if ( !c )
{
x3dom.debug.doLog( "Assertion failed in " +
x3dom.debug.assert.caller.name + ": " +
msg, x3dom.debug.ERROR );
}
},
/**
* @brief Checks the type of a given object.
*
* @param obj the object to check.
* @returns one of; "boolean", "number", "string", "object", "function", or "null".
*/
typeOf : function ( obj )
{
var type = typeof obj;
return type === "object" && !obj ? "null" : type;
},
/**
* @brief Checks if a property of a specified object has the given type.
*
* @param obj the object to check.
* @param name the property name.
* @param type the property type (optional, default is "function").
* @returns true if the property exists and has the specified type, otherwise false.
*/
exists : function ( obj, name, type )
{
type = type || "function";
return ( obj ? this.typeOf( obj[ name ] ) : "null" ) === type;
},
/**
* @brief Dumps all members of the given object.
*
* @param node
*/
dumpFields : function ( node )
{
var str = "";
for ( var fName in node )
{
str += ( fName + ", " );
}
str += "\n";
x3dom.debug.logInfo( str );
return str;
}
};
// Call the setup function to... umm, well, setup x3dom.debug
x3dom.debug.setup();
/*
* X3DOM JavaScript Library
* http://www.x3dom.org
*
* (C)2009 Fraunhofer IGD, Darmstadt, Germany
* Dual licensed under the MIT and GPL
*
* Based on code originally provided by
* Philip Taylor: http://philip.html5.org
*/
/**
* The canvas object wraps the HTML canvas x3dom draws
* @constructs x3dom.X3DCanvas
* @param {Object} [x3dElement] - x3d element rendering into the canvas
* @param {String} [canvasIdx] - id of HTML canvas
*/
x3dom.X3DCanvas = function ( x3dElem, canvasIdx )
{
var that = this;
/**
* The index of the HTML canvas
* @member {String} _canvasIdx
*/
this._canvasIdx = canvasIdx;
/**
* The X3D Element
* @member {X3DElement} x3dElem
*/
this.x3dElem = x3dElem;
/**
* The current canvas dimensions
* @member {Array} _current_dim
*/
this._current_dim = [ 0, 0 ];
// for FPS measurements
this.fps_t0 = Date.now();
this.lastTimeFPSWasTaken = 0;
this.framesSinceLastTime = 0;
this._totalTime = 0;
this._elapsedTime = 0;
this.doc = null;
this.isSessionSupportedPromise = null;
this.xrSession = null;
this.xrReferenceSpace = null;
this.supportsPassiveEvents = false;
this.devicePixelRatio = window.devicePixelRatio || 1;
this.lastMousePos = { x: 0, y: 0 };
//try to determine behavior of certain DOMNodeInsertedEvent:
//IE11 dispatches one event for each node in an inserted subtree, other browsers use a single event per subtree
x3dom.caps.DOMNodeInsertedEvent_perSubtree = !( navigator.userAgent.indexOf( "MSIE" ) != -1 ||
navigator.userAgent.indexOf( "Trident" ) != -1 );
// allow listening for (size) changes
x3dElem.__setAttribute = x3dElem.setAttribute;
//adds setAttribute function for width and height to the X3D element
x3dElem.setAttribute = function ( attrName, newVal )
{
this.__setAttribute( attrName, newVal );
// scale resolution so device pixel are used rather then css pixels
newVal = parseInt( newVal );
switch ( attrName )
{
case "width":
that.canvas.setAttribute( "width", newVal * that.devicePixelRatio );
if ( that.doc && that.doc._viewarea )
{
that.doc._viewarea._width = parseInt( that.canvas.getAttribute( "width" ), 0 );
that.doc.needRender = true;
}
break;
case "height":
that.canvas.setAttribute( "height", newVal * that.devicePixelRatio );
if ( that.doc && that.doc._viewarea )
{
that.doc._viewarea._height = parseInt( that.canvas.getAttribute( "height" ), 0 );
that.doc.needRender = true;
}
break;
default:
break;
}
};
this.backend = this.x3dElem.getAttribute( "backend" );
this.backend = ( this.backend ) ? this.backend.toLowerCase() : "none";
this.canvas = this._createHTMLCanvas( x3dElem );
x3dom.debug.appendElement( x3dElem );
this.canvas.parent = this;
this.gl = this._initContext( this.canvas );
this.backend = "webgl";
if ( this.gl == null )
{
this.hasRuntime = false;
this._createInitFailedDiv( x3dElem );
return;
}
x3dom.caps.BACKEND = this.backend;
var runtimeEnabled = x3dElem.getAttribute( "runtimeEnabled" );
if ( runtimeEnabled !== null )
{
this.hasRuntime = ( runtimeEnabled.toLowerCase() == "true" );
}
else
{
this.hasRuntime = x3dElem.hasRuntime;
}
/**
* STATS VIEWER STUFF
* TODO MOVE IT TO MAIN.JS
*/
this.showStat = x3dElem.getAttribute( "showStat" );
this.stateViewer = new x3dom.States( x3dElem );
if ( this.showStat !== null && this.showStat == "true" )
{
this.stateViewer.display( true );
}
this.x3dElem.appendChild( this.stateViewer.viewer );
// progress bar
this.showProgress = x3dElem.getAttribute( "showProgress" );
this.progressDiv = this._createProgressDiv();
this.progressDiv.style.display = ( this.showProgress !== null && this.showProgress == "true" ) ? "flex" : "none";
this.x3dElem.appendChild( this.progressDiv );
// vr button
this.vrDiv = this._createVRDiv();
this.x3dElem.appendChild( this.vrDiv );
// touch visualization
this.showTouchpoints = x3dElem.getAttribute( "showTouchpoints" );
this.showTouchpoints = this.showTouchpoints ? this.showTouchpoints : false;
// disable touch events
this.disableTouch = x3dElem.getAttribute( "disableTouch" );
this.disableTouch = this.disableTouch ? ( this.disableTouch.toLowerCase() == "true" ) : false;
this.disableKeys = x3dElem.getAttribute( "disableKeys" );
this.disableKeys = this.disableKeys ? ( this.disableKeys.toLowerCase() == "true" ) : false;
this.disableRightDrag = x3dElem.getAttribute( "disableRightDrag" );
this.disableRightDrag = this.disableRightDrag ? ( this.disableRightDrag.toLowerCase() == "true" ) : false;
this.disableLeftDrag = x3dElem.getAttribute( "disableLeftDrag" );
this.disableLeftDrag = this.disableLeftDrag ? ( this.disableLeftDrag.toLowerCase() == "true" ) : false;
this.disableMiddleDrag = x3dElem.getAttribute( "disableMiddleDrag" );
this.disableMiddleDrag = this.disableMiddleDrag ? ( this.disableMiddleDrag.toLowerCase() == "true" ) : false;
this.detectPassiveEvents();
this.bindEventListeners();
};
x3dom.X3DCanvas.prototype.detectPassiveEvents = function ()
{
if ( typeof window !== "undefined" && typeof window.addEventListener === "function" )
{
var passive = false;
var options = Object.defineProperty( {}, "passive",
{
get : function () { passive = true; }
} );
// note: have to set and remove a no-op listener instead of null
// (which was used previously), becasue Edge v15 throws an error
// when providing a null callback.
// https://github.com/rafrex/detect-passive-events/pull/3
var noop = function () {};
window.addEventListener( "testPassiveEventSupport", noop, options );
window.removeEventListener( "testPassiveEventSupport", noop, options );
this.supportsPassiveEvents = passive;
}
};
x3dom.X3DCanvas.prototype.bindEventListeners = function ()
{
var that = this;
this.onMouseDown = function ( evt )
{
if ( !this.isMulti )
{
this.focus();
this.classList.add( "x3dom-canvas-mousedown" );
switch ( evt.button )
{
case 0: this.mouse_button = 1; break; //left
case 1: this.mouse_button = 4; break; //middle
case 2: this.mouse_button = 2; break; //right
default: this.mouse_button = 0; break;
}
if ( evt.shiftKey ) { this.mouse_button = 1; }
if ( evt.ctrlKey ) { this.mouse_button = 4; }
if ( evt.altKey ) { this.mouse_button = 2; }
var pos = this.parent.mousePosition( evt );
this.mouse_drag_x = pos.x;
this.mouse_drag_y = pos.y;
this.mouse_dragging = true;
this.parent.doc.onMousePress( that.gl, this.mouse_drag_x, this.mouse_drag_y, this.mouse_button );
this.parent.doc.needRender = true;
}
};
this.onMouseUp = function ( evt )
{
if ( !this.isMulti )
{
var prev_mouse_button = this.mouse_button;
this.classList.remove( "x3dom-canvas-mousedown" );
this.mouse_button = 0;
this.mouse_dragging = false;
this.parent.doc.onMouseRelease( that.gl, this.mouse_drag_x, this.mouse_drag_y, this.mouse_button, prev_mouse_button );
this.parent.doc.needRender = true;
}
};
this.onMouseOver = function ( evt )
{
if ( !this.isMulti )
{
this.mouse_button = 0;
this.mouse_dragging = false;
this.parent.doc.onMouseOver( that.gl, this.mouse_drag_x, this.mouse_drag_y, this.mouse_button );
this.parent.doc.needRender = true;
}
};
this.onMouseOut = function ( evt )
{
if ( !this.isMulti )
{
this.mouse_button = 0;
this.mouse_dragging = false;
this.classList.remove( "x3dom-canvas-mousedown" );
this.parent.doc.onMouseOut( that.gl, this.mouse_drag_x, this.mouse_drag_y, this.mouse_button );
this.parent.doc.needRender = true;
}
};
this.onDoubleClick = function ( evt )
{
if ( !this.isMulti )
{
this.mouse_button = 0;
var pos = this.parent.mousePosition( evt );
this.mouse_drag_x = pos.x;
this.mouse_drag_y = pos.y;
this.mouse_dragging = false;
this.parent.doc.onDoubleClick( that.gl, this.mouse_drag_x, this.mouse_drag_y );
this.parent.doc.needRender = true;
}
};
this.onMouseMove = function ( evt )
{
if ( !this.isMulti )
{
var pos = this.parent.mousePosition( evt );
if ( pos.x != that.lastMousePos.x || pos.y != that.lastMousePos.y )
{
that.lastMousePos = pos;
this.mouse_drag_x = pos.x;
this.mouse_drag_y = pos.y;
if ( this.mouse_dragging )
{
if ( evt.shiftKey ) { this.mouse_button = 1; }
if ( evt.ctrlKey ) { this.mouse_button = 4; }
if ( evt.altKey ) { this.mouse_button = 2; }
if ( this.mouse_button == 1 && !this.parent.disableLeftDrag ||
this.mouse_button == 2 && !this.parent.disableRightDrag ||
this.mouse_button == 4 && !this.parent.disableMiddleDrag )
{
this.parent.doc.onDrag( that.gl, this.mouse_drag_x, this.mouse_drag_y, this.mouse_button );
}
}
else
{
this.parent.doc.onMove( that.gl, this.mouse_drag_x, this.mouse_drag_y, this.mouse_button );
}
this.parent.doc.needRender = true;
// deliberately different for performance reasons
evt.preventDefault();
evt.stopPropagation();
}
}
};
this.onDOMMouseScroll = function ( evt )
{
if ( !this.isMulti )
{
this.focus();
var originalY = this.parent.mousePosition( evt ).y;
if ( this.parent.doc._scene.getNavigationInfo()._vf.reverseScroll == true )
{
this.mouse_drag_y -= 2 * evt.detail;
}
else
{
this.mouse_drag_y += 2 * evt.detail;
}
this.parent.doc.onWheel( that.gl, this.mouse_drag_x, this.mouse_drag_y, originalY );
this.parent.doc.needRender = true;
evt.preventDefault();
evt.stopPropagation();
}
};
this.onKeyPress = function ( evt )
{
if ( !this.parent.disableKeys )
{
evt.preventDefault();
this.parent.doc.onKeyPress( evt.charCode );
}
this.parent.doc.needRender = true;
};
this.onMouseWheel = function ( evt )
{
if ( !this.isMulti )
{
this.focus();
var originalY = this.parent.mousePosition( evt ).y;
if ( this.parent.doc._scene.getNavigationInfo()._vf.reverseScroll == true )
{
this.mouse_drag_y += 0.1 * evt.wheelDelta;
}
else
{
this.mouse_drag_y -= 0.1 * evt.wheelDelta;
}
this.parent.doc.onWheel( that.gl, this.mouse_drag_x, this.mouse_drag_y, originalY );
this.parent.doc.needRender = true;
evt.preventDefault();
evt.stopPropagation();
}
};
this.onKeyUp = function ( evt )
{
if ( !this.parent.disableKeys )
{
this.parent.doc.onKeyUp( evt.keyCode );
}
this.parent.doc.needRender = true;
};
this.onKeyDown = function ( evt )
{
if ( !this.parent.disableKeys )
{
this.parent.doc.onKeyDown( evt.keyCode );
}
this.parent.doc.needRender = true;
};
if ( this.canvas !== null && this.gl !== null && this.hasRuntime )
{
// event handler for mouse interaction
this.canvas.mouse_dragging = false;
this.canvas.mouse_button = 0;
this.canvas.mouse_drag_x = 0;
this.canvas.mouse_drag_y = 0;
this.canvas.isMulti = false; // don't interfere with multi-touch
this.canvas.oncontextmenu = function ( evt )
{
evt.preventDefault();
evt.stopPropagation();
return false;
};
// TODO: handle context lost events properly
this.canvas.addEventListener( "webglcontextlost", function ( event )
{
x3dom.debug.logError( "WebGL context lost" );
event.preventDefault();
}, false );
this.canvas.addEventListener( "webglcontextrestored", function ( event )
{
x3dom.debug.logError( "recover WebGL state and resources on context lost NYI" );
event.preventDefault();
}, false );
// Mouse Events
this.canvas.addEventListener( "mousedown", this.onMouseDown, false );
this.canvas.addEventListener( "mouseup", this.onMouseUp, false );
this.canvas.addEventListener( "mouseover", this.onMouseOver, false );
this.canvas.addEventListener( "mouseout", this.onMouseOut, false );
this.canvas.addEventListener( "dblclick", this.onDoubleClick, false );
this.canvas.addEventListener( "mousemove", this.onMouseMove, false );
this.canvas.addEventListener( "DOMMouseScroll", this.onDOMMouseScroll, false );
this.canvas.addEventListener( "mousewheel", this.onMouseWheel, this.supportsPassiveEvents ? {passive: false} : false );
// Key Events
this.canvas.addEventListener( "keypress", this.onKeyPress, true );
// in webkit special keys are only handled on key-up
this.canvas.addEventListener( "keyup", this.onKeyUp, true );
this.canvas.addEventListener( "keydown", this.onKeyDown, true );
// Multitouch Events
var touches =
{
numTouches : 0,
firstTouchTime : Date.now(),
firstTouchPoint : new x3dom.fields.SFVec2f( 0, 0 ),
lastPos : new x3dom.fields.SFVec2f(),
lastDrag : new x3dom.fields.SFVec2f(),
lastMiddle : new x3dom.fields.SFVec2f(),
lastSquareDistance : 0,
lastAngle : 0,
lastLayer : [],
examineNavType : 1,
calcAngle : function ( vector )
{
var rotation = vector.normalize().dot( new x3dom.fields.SFVec2f( 1, 0 ) );
rotation = Math.acos( rotation );
if ( vector.y < 0 )
{rotation = Math.PI + ( Math.PI - rotation );}
return rotation;
},
disableTouch : this.disableTouch,
// set a marker in HTML so we can track the position of the finger visually
visMarker : this.showTouchpoints,
visMarkerBag : [],
visualizeTouches : function ( evt )
{
if ( !this.visMarker )
{return;}
var touchBag = [];
var marker = null;
for ( var i = 0; i < evt.touches.length; i++ )
{
var id = evt.touches[ i ].identifier || evt.touches[ i ].streamId;
if ( !id ) {id = 0;}
var index = this.visMarkerBag.indexOf( id );
if ( index >= 0 )
{
marker = document.getElementById( "visMarker" + id );
marker.style.left = ( evt.touches[ i ].pageX ) + "px";
marker.style.top = ( evt.touches[ i ].pageY ) + "px";
}
else
{
marker = document.createElement( "div" );
marker.appendChild( document.createTextNode( "#" + id ) );
marker.id = "visMarker" + id;
marker.className = "x3dom-touch-marker";
document.body.appendChild( marker );
index = this.visMarkerBag.length;
this.visMarkerBag[ index ] = id;
}
touchBag.push( id );
}
for ( var j = this.visMarkerBag.length - 1; j >= 0; j-- )
{
var oldId = this.visMarkerBag[ j ];
if ( touchBag.indexOf( oldId ) < 0 )
{
this.visMarkerBag.splice( j, 1 );
marker = document.getElementById( "visMarker" + oldId );
document.body.removeChild( marker );
}
}
}
};
// === Touch Start ===
var touchStartHandler = function ( evt, doc )
{
this.isMulti = true;
evt.preventDefault();
touches.visualizeTouches( evt );
this.focus();
if ( doc == null )
{doc = this.parent.doc;}
var navi = doc._scene.getNavigationInfo();
switch ( navi.getType() )
{
case "examine":
touches.examineNavType = 1;
break;
case "turntable":
touches.examineNavType = 2;
break;
default:
touches.examineNavType = 0;
break;
}
touches.lastLayer = [];
var i,
pos;
for ( i = 0; i < evt.touches.length; i++ )
{
pos = this.parent.mousePosition( evt.touches[ i ] );
touches.lastLayer.push( [ evt.touches[ i ].identifier, new x3dom.fields.SFVec2f( pos.x, pos.y ) ] );
}
if ( touches.numTouches < 1 && evt.touches.length == 1 )
{
touches.numTouches = 1;
touches.lastDrag = new x3dom.fields.SFVec2f( evt.touches[ 0 ].screenX, evt.touches[ 0 ].screenY );
}
else if ( touches.numTouches < 2 && evt.touches.length >= 2 )
{
touches.numTouches = 2;
var touch0 = new x3dom.fields.SFVec2f( evt.touches[ 0 ].screenX, evt.touches[ 0 ].screenY );
var touch1 = new x3dom.fields.SFVec2f( evt.touches[ 1 ].screenX, evt.touches[ 1 ].screenY );
var distance = touch1.subtract( touch0 );
var middle = distance.multiply( 0.5 ).add( touch0 );
var squareDistance = distance.dot( distance );
touches.lastMiddle = middle;
touches.lastSquareDistance = squareDistance;
touches.lastAngle = touches.calcAngle( distance );
touches.lastPos = this.parent.mousePosition( evt.touches[ 0 ] );
}
// update scene bbox
doc._scene.updateVolume();
if ( touches.examineNavType == 1 )
{
for ( i = 0; i < evt.touches.length; i++ )
{
pos = this.parent.mousePosition( evt.touches[ i ] );
doc.onPick( that.gl, pos.x, pos.y );
//Trigger onmouseover to collect affected X3DPointingDeviceSensorNodes
doc._viewarea.prepareEvents( pos.x, pos.y, 0, "onmouseover" );
doc._viewarea.prepareEvents( pos.x, pos.y, 1, "onmousedown" );
doc._viewarea._pickingInfo.lastClickObj = doc._viewarea._pickingInfo.pickObj;
}
}
else if ( evt.touches.length )
{
pos = this.parent.mousePosition( evt.touches[ 0 ] );
doc.onMousePress( that.gl, pos.x, pos.y, 1 ); // 1 means left mouse button
}
doc.needRender = true;
};
// === Touch Move ===
var touchMoveHandler = function ( evt, doc )
{
evt.preventDefault();
touches.visualizeTouches( evt );
if ( doc == null )
{doc = this.parent.doc;}
var pos = null;
var rotMatrix = null;
var touch0,
touch1,
distance,
middle,
squareDistance,
deltaMiddle,
deltaZoom,
deltaMove;
if ( touches.examineNavType == 1 )
{
// one finger: x/y rotation
if ( evt.touches.length == 1 )
{
var currentDrag = new x3dom.fields.SFVec2f( evt.touches[ 0 ].screenX, evt.touches[ 0 ].screenY );
var deltaDrag = currentDrag.subtract( touches.lastDrag );
touches.lastDrag = currentDrag;
var mx = x3dom.fields.SFMatrix4f.rotationY( deltaDrag.x / 100 );
var my = x3dom.fields.SFMatrix4f.rotationX( deltaDrag.y / 100 );
rotMatrix = mx.mult( my );
doc.onMoveView( that.gl, evt, touches, null, rotMatrix );
//Trigger onmousemove to notify X3DPointingDeviceSensorNodes
pos = this.parent.mousePosition( evt.touches[ 0 ] );
doc.onPick( that.gl, pos.x, pos.y );
doc._viewarea.prepareEvents( pos.x, pos.y, 1, "onmousemove" );
}
// two fingers: scale, translation, rotation around view (z) axis
else if ( evt.touches.length >= 2 )
{
touch0 = new x3dom.fields.SFVec2f( evt.touches[ 0 ].screenX, evt.touches[ 0 ].screenY );
touch1 = new x3dom.fields.SFVec2f( evt.touches[ 1 ].screenX, evt.touches[ 1 ].screenY );
distance = touch1.subtract( touch0 );
middle = distance.multiply( 0.5 ).add( touch0 );
squareDistance = distance.dot( distance );
deltaMiddle = middle.subtract( touches.lastMiddle );
deltaZoom = squareDistance - touches.lastSquareDistance;
deltaMove = new x3dom.fields.SFVec3f(
deltaMiddle.x / screen.width, -deltaMiddle.y / screen.height,
deltaZoom / ( screen.width * screen.height * 0.2 ) );
var rotation = touches.calcAngle( distance );
var angleDelta = touches.lastAngle - rotation;
touches.lastAngle = rotation;
rotMatrix = x3dom.fields.SFMatrix4f.rotationZ( angleDelta );
touches.lastMiddle = middle;
touches.lastSquareDistance = squareDistance;
doc.onMoveView( that.gl, evt, touches, deltaMove, rotMatrix );
}
}
else if ( evt.touches.length )
{
if ( touches.examineNavType == 2 && evt.touches.length >= 2 )
{
touch0 = new x3dom.fields.SFVec2f( evt.touches[ 0 ].screenX, evt.touches[ 0 ].screenY );
touch1 = new x3dom.fields.SFVec2f( evt.touches[ 1 ].screenX, evt.touches[ 1 ].screenY );
distance = touch1.subtract( touch0 );
squareDistance = distance.dot( distance );
deltaZoom = ( squareDistance - touches.lastSquareDistance ) / ( 0.1 * ( screen.width + screen.height ) );
touches.lastPos.y += deltaZoom;
touches.lastSquareDistance = squareDistance;
doc.onDrag( that.gl, touches.lastPos.x, touches.lastPos.y, 2 );
}
else
{
pos = this.parent.mousePosition( evt.touches[ 0 ] );
doc.onDrag( that.gl, pos.x, pos.y, 1 );
}
}
doc.needRender = true;
};
// === Touch end ===
var touchEndHandler = function ( evt, doc )
{
this.isMulti = false;
if ( evt.cancelable )
{
evt.preventDefault();
}
touches.visualizeTouches( evt );
if ( doc == null )
{doc = this.parent.doc;}
doc._viewarea._isMoving = false;
// reinit first finger for rotation
if ( touches.numTouches == 2 && evt.touches.length == 1 )
{touches.lastDrag = new x3dom.fields.SFVec2f( evt.touches[ 0 ].screenX, evt.touches[ 0 ].screenY );}
// if all touches are released, reset the list of currently affected pointing sensors
if ( evt.touches.length == 0 )
{
var affectedPointingSensorsList = doc._nodeBag.affectedPointingSensors;
for ( var i = 0; i < affectedPointingSensorsList.length; ++i )
{
affectedPointingSensorsList[ i ].pointerReleased();
}
doc._nodeBag.affectedPointingSensors = [];
}
var dblClick = false;
if ( evt.touches.length < 2 )
{
if ( touches.numTouches == 1 )
{dblClick = true;}
touches.numTouches = evt.touches.length;
}
if ( touches.examineNavType == 1 )
{
for ( var i = 0; i < touches.lastLayer.length; i++ )
{
var pos = touches.lastLayer[ i ][ 1 ];
doc.onPick( that.gl, pos.x, pos.y );
if ( doc._scene._vf.pickMode.toLowerCase() !== "box" )
{
doc._viewarea.prepareEvents( pos.x, pos.y, 1, "onmouseup" );
doc._viewarea._pickingInfo.lastClickObj = doc._viewarea._pickingInfo.pickObj;
// click means that mousedown _and_ mouseup were detected on same element
if ( doc._viewarea._pickingInfo.pickObj &&
doc._viewarea._pickingInfo.pickObj ===
doc._viewarea._pickingInfo.lastClickObj )
{
doc._viewarea.prepareEvents( pos.x, pos.y, 1, "onclick" );
}
}
else
{
var line = doc._viewarea.calcViewRay( pos.x, pos.y );
var isect = doc._scene.doIntersect( line );
var obj = line.hitObject;
if ( isect && obj )
{
doc._viewarea._pick.setValues( line.hitPoint );
doc._viewarea.checkEvents( obj, pos.x, pos.y, 1, "onclick" );
x3dom.debug.logInfo( "Hit '" + obj._xmlNode.localName + "/ " +
obj._DEF + "' at pos " + doc._viewarea._pick );
}
}
}
if ( dblClick )
{
var now = Date.now();
var dist = touches.firstTouchPoint.subtract( touches.lastDrag ).length();
if ( dist < 18 && now - touches.firstTouchTime < 180 )
{doc.onDoubleClick( that.gl, 0, 0 );}
touches.firstTouchTime = now;
touches.firstTouchPoint = touches.lastDrag;
}
}
else if ( touches.lastLayer.length )
{
pos = touches.lastLayer[ 0 ][ 1 ];
doc.onMouseRelease( that.gl, pos.x, pos.y, 0, 1 );
}
doc.needRender = true;
};
if ( !this.disableTouch )
{
// w3c / apple touch events (in Chrome via chrome://flags)
this.canvas.addEventListener( "touchstart", touchStartHandler, this.supportsPassiveEvents ? {passive: false} : true );
this.canvas.addEventListener( "touchmove", touchMoveHandler, this.supportsPassiveEvents ? {passive: false} : true );
this.canvas.addEventListener( "touchend", touchEndHandler, true );
}
}
};
//----------------------------------------------------------------------------------------------------------------------
/**
* Creates the WebGL context and returns it
* @returns {WebGLContext} gl
* @param {HTMLCanvas} canvas
*/
x3dom.X3DCanvas.prototype._initContext = function ( canvas )
{
x3dom.debug.logInfo( "Initializing X3DCanvas for [" + canvas.id + "]" );
var gl = x3dom.gfx_webgl( canvas, this.x3dElem );
if ( !gl )
{
x3dom.debug.logError( "No 3D context found..." );
this.x3dElem.removeChild( canvas );
return null;
}
else
{
var webglVersion = parseFloat( x3dom.caps.VERSION.match( /\d+\.\d+/ )[ 0 ] );
if ( webglVersion < 1.0 )
{
x3dom.debug.logError( "WebGL version " + x3dom.caps.VERSION +
" lacks important WebGL/GLSL features needed for shadows, special vertex attribute types, etc.!" );
}
}
return gl;
};
//----------------------------------------------------------------------------------------------------------------------
/**
* Creates a param node and adds it to the target node's children
* @param {String} node - the target node
* @param {String} name - the name for the parameter
* @param {String} value - the value for the parameter
*/
x3dom.X3DCanvas.prototype.appendParam = function ( node, name, value )
{
var param = document.createElement( "param" );
param.setAttribute( "name", name );
param.setAttribute( "value", value );
node.appendChild( param );
};
//----------------------------------------------------------------------------------------------------------------------
/**
* Creates a div to inform the user that the initialization failed
* @param {String} x3dElem - the X3D element
*/
x3dom.X3DCanvas.prototype._createInitFailedDiv = function ( x3dElem )
{
var div = document.createElement( "div" );
div.setAttribute( "id", "x3dom-create-init-failed" );
div.style.width = x3dElem.getAttribute( "width" );
div.style.height = x3dElem.getAttribute( "height" );
div.style.backgroundColor = "#C00";
div.style.color = "#FFF";
div.style.fontSize = "20px";
div.style.fontWidth = "bold";
div.style.padding = "10px 10px 10px 10px";
div.style.display = "inline-block";
div.style.fontFamily = "Helvetica";
div.style.textAlign = "center";
div.appendChild( document.createTextNode( "Your Browser does not support X3DOM" ) );
div.appendChild( document.createElement( "br" ) );
div.appendChild( document.createTextNode( "Read more about Browser support on:" ) );
div.appendChild( document.createElement( "br" ) );
var link = document.createElement( "a" );
link.setAttribute( "href", "http://www.x3dom.org/?page_id=9" );
link.appendChild( document.createTextNode( "X3DOM | Browser Support" ) );
div.appendChild( link );
// check if "altImg" is specified on x3d element and if so use it as background
var altImg = x3dElem.getAttribute( "altImg" ) || null;
if ( altImg )
{
var altImgObj = new Image();
altImgObj.src = altImg;
div.style.backgroundImage = "url(" + altImg + ")";
div.style.backgroundRepeat = "no-repeat";
div.style.backgroundPosition = "50% 50%";
}
x3dElem.appendChild( div );
x3dom.debug.logError( "Your Browser does not support X3DOM!" );
};
//----------------------------------------------------------------------------------------------------------------------
/**
* Creates the HTML canvas used as render target
* @returns {HTMLCanvas} - the created canvas
* @param {HTMLNode} x3dElem - the X3D root node
*/
x3dom.X3DCanvas.prototype._createHTMLCanvas = function ( x3dElem )
{
x3dom.debug.logInfo( "Creating canvas for (X)3D element..." );
var canvas = document.createElement( "canvas" );
canvas.setAttribute( "class", "x3dom-canvas" );
// check if user wants to style the X3D element
var userStyle = x3dElem.getAttribute( "style" );
if ( userStyle )
{
x3dom.debug.logInfo( "Inline X3D styles detected" );
}
// check if user wants to attach events to the X3D element
var evtArr = [
"onmousedown",
"onmousemove",
"onmouseout",
"onmouseover",
"onmouseup",
"onclick",
"ondblclick",
"onkeydown",
"onkeypress",
"onkeyup",
// w3c touch: http://www.w3.org/TR/2011/WD-touch-events-20110505/
"ontouchstart",
"ontouchmove",
"ontouchend",
"ontouchcancel",
"ontouchleave",
"ontouchenter",
// drag and drop, requires 'draggable' source property set true (usually of an img)
"ondragstart",
"ondrop",
"ondragover"
];
// TODO; handle attribute event handlers dynamically during runtime
//this step is necessary because of some weird behavior in some browsers:
//we need a canvas element on startup to make every callback (e.g., 'onmousemove') work,
//which was previously set for the canvas' outer elements
for ( var i = 0; i < evtArr.length; i++ )
{
var evtName = evtArr[ i ];
var userEvt = x3dElem.getAttribute( evtName );
if ( userEvt )
{
x3dom.debug.logInfo( evtName + ", " + userEvt );
canvas.setAttribute( evtName, userEvt );
//remove the event attribute from the X3D element to prevent duplicate callback invocation
x3dElem.removeAttribute( evtName );
}
}
var userProp = x3dElem.getAttribute( "draggable" );
if ( userProp )
{
x3dom.debug.logInfo( "draggable=" + userProp );
canvas.setAttribute( "draggable", userProp );
}
// workaround since one cannot find out which handlers are registered
if ( !x3dElem.__addEventListener && !x3dElem.__removeEventListener )
{
x3dElem.__addEventListener = x3dElem.addEventListener;
x3dElem.__removeEventListener = x3dElem.removeEventListener;
// helpers to propagate the element's listeners
x3dElem.addEventListener = function ( type, func, phase )
{
var j,
found = false;
for ( j = 0; j < evtArr.length && !found; j++ )
{
if ( evtArr[ j ] === type )
{
found = true;
}
}
if ( found )
{
x3dom.debug.logInfo( "addEventListener for div.on" + type );
canvas.addEventListener( type, func, phase );
}
else
{
x3dom.debug.logInfo( "addEventListener for X3D.on" + type );
this.__addEventListener( type, func, phase );
}
};
x3dElem.removeEventListener = function ( type, func, phase )
{
var j,
found = false;
for ( j = 0; j < evtArr.length && !found; j++ )
{
if ( evtArr[ j ] === type )
{
found = true;
}
}
if ( found )
{
x3dom.debug.logInfo( "removeEventListener for div.on" + type );
canvas.removeEventListener( type, func, phase );
}
else
{
x3dom.debug.logInfo( "removeEventListener for X3D.on" + type );
this.__removeEventListener( type, func, phase );
}
};
}
//add element-specific (global) events for the X3D tag
if ( x3dElem.hasAttribute( "ondownloadsfinished" ) )
{
x3dElem.addEventListener( "downloadsfinished",
function ()
{
var eventObject = {
target : x3dElem,
type : "downloadsfinished"
};
var funcStr = x3dElem.getAttribute( "ondownloadsfinished" );
var func = new Function( "event", funcStr );
func.call( x3dElem, eventObject );
},
true );
}
x3dElem.appendChild( canvas );
// If the X3D element has an id attribute, append "_canvas"
// to it and and use that as the id for the canvas
var id = x3dElem.getAttribute( "id" );
if ( id !== null )
{
canvas.id = "x3dom-" + id + "-canvas";
}
else
{
// If the X3D element does not have an id... do what?
// For now check the date for creating a (hopefully) unique id
var index = Date.now();
canvas.id = "x3dom-" + index + "-canvas";
}
// fix for undefined style attribute when using deprecated .xhtml files
x3dElem.style = x3dElem.style || {};
// Apply the width and height of the X3D element to the canvas
var w,
h;
if ( ( w = x3dElem.getAttribute( "width" ) ) !== null )
{
//Attention: pbuffer dim is _not_ derived from style attribs!
if ( w.indexOf( "%" ) >= 0 )
{
x3dom.debug.logWarning( "The width attribute is to be specified in pixels not in percent." );
}
canvas.style.width = w;
x3dElem.style.width = w;
canvas.setAttribute( "width", w );
}
if ( ( h = x3dElem.getAttribute( "height" ) ) !== null )
{
//Attention: pbuffer dim is _not_ derived from style attribs!
if ( h.indexOf( "%" ) >= 0 )
{
x3dom.debug.logWarning( "The height attribute is to be specified in pixels not in percent." );
}
canvas.style.height = h;
x3dElem.style.height = h;
canvas.setAttribute( "height", h );
}
// http://snook.ca/archives/accessibility_and_usability/elements_focusable_with_tabindex
canvas.setAttribute( "tabindex", "0" );
// canvas.focus(); ???why - it is necessary - makes touch events break???
return canvas;
};
/**
* Watches for a resize of the canvas and sets the current dimensions
*/
x3dom.X3DCanvas.prototype._watchForResize = function ( )
{
if ( this.xrSession )
{
return;
}
var new_dim = [
parseInt( x3dom.getStyle( this.canvas, "width" ) ) || 0,
parseInt( x3dom.getStyle( this.canvas, "height" ) ) || 0
];
if ( ( this._current_dim[ 0 ] != new_dim[ 0 ] ) || ( this._current_dim[ 1 ] != new_dim[ 1 ] ) )
{
this._current_dim = new_dim;
this.x3dElem.setAttribute( "width", new_dim[ 0 ] + "px" );
this.x3dElem.setAttribute( "height", new_dim[ 1 ] + "px" );
}
};
//----------------------------------------------------------------------------------------------------------------------
/**
* Creates the div for progression visualization
*/
x3dom.X3DCanvas.prototype._createProgressDiv = function ()
{
var progressDiv = document.createElement( "div" );
progressDiv.setAttribute( "class", "x3dom-progress" );
var spinnerDiv = document.createElement( "div" );
spinnerDiv.setAttribute( "class", "x3dom-progress-spinner" );
progressDiv.appendChild( spinnerDiv );
var text = document.createElement( "div" );
text.setAttribute( "id", "x3domProgessCount" );
text.appendChild( document.createTextNode( "Loading..." ) );
progressDiv.appendChild( text );
progressDiv.oncontextmenu = progressDiv.onmousedown = function ( evt )
{
evt.preventDefault();
evt.stopPropagation();
return false;
};
return progressDiv;
};
/**
* Creates the div for progression visualization
*/
x3dom.X3DCanvas.prototype._createVRDiv = function ()
{
var vrDiv = document.createElement( "div" );
vrDiv.setAttribute( "class", "x3dom-vr" );
vrDiv.onclick = ( e ) =>
{
this.x3dElem.runtime.toggleVR();
};
vrDiv.oncontextmenu = function ( evt )
{
evt.preventDefault();
evt.stopPropagation();
return false;
};
vrDiv.title = "Toggle VR";
return vrDiv;
};
//----------------------------------------------------------------------------------------------------------------------
/** Helper that converts a point from node coordinates to page coordinates
FIXME: does NOT work when x3dom.css is not included so that x3d element is not floating
*/
x3dom.X3DCanvas.prototype.mousePosition = function ( evt )
{
var rect = evt.target.getBoundingClientRect();
var offsetX = Math.round( evt.clientX - rect.left ) * this.devicePixelRatio;
var offsetY = Math.round( evt.clientY - rect.top ) * this.devicePixelRatio;
return new x3dom.fields.SFVec2f( offsetX, offsetY );
};
//----------------------------------------------------------------------------------------------------------------------
/** Is called in the main loop after every frame
*/
x3dom.X3DCanvas.prototype.tick = function ( timestamp, xrFrame )
{
var that = this;
this._elapsedTime = ( this._totalTime ) ? timestamp - this._totalTime : 0;
this._totalTime = timestamp;
var runtime = this.x3dElem.runtime;
var d = Date.now();
var diff = d - this.lastTimeFPSWasTaken;
var fps = 1000.0 / ( d - this.fps_t0 );
this.fps_t0 = d;
// update routes and stuff
this.doc.advanceTime( d / 1000.0 );
var animD = Date.now() - d;
if ( this.doc.hasAnimationStateChanged() )
{
if ( this.doc.isAnimating() )
{
runtime.onAnimationStarted();
}
else
{
runtime.onAnimationFinished();
}
}
if ( this.doc.needRender || xrFrame )
{
// calc average frames per second
if ( diff >= 1000 )
{
runtime.fps = this.framesSinceLastTime / ( diff / 1000.0 );
runtime.addMeasurement( "FPS", runtime.fps );
this.framesSinceLastTime = 0;
this.lastTimeFPSWasTaken = d;
}
this.framesSinceLastTime++;
runtime.addMeasurement( "ANIM", animD );
if ( runtime.isReady == false )
{
runtime.ready();
runtime.isReady = true;
}
runtime.enterFrame( {"total": this._totalTime, "elapsed": this._elapsedTime} );
if ( !xrFrame )
{
this.doc.needRender = false;
}
this.doc.render( this.gl, this.getVRFrameData( xrFrame ) );
if ( !this.doc._scene._vf.doPickPass )
{
runtime.removeMeasurement( "PICKING" );
}
runtime.exitFrame( {"total": this._totalTime, "elapsed": this._elapsedTime} );
}
if ( this.progressDiv )
{
if ( this.doc.downloadCount > 0 )
{
runtime.addInfo( "#LOADS:", this.doc.downloadCount );
}
else
{
runtime.removeInfo( "#LOADS:" );
}
if ( this.doc.properties.getProperty( "showProgress" ) !== "false" )
{
if ( this.progressDiv )
{
var count = Math.max( +this.doc.downloadCount, 0 );
this.progressDiv.childNodes[ 1 ].textContent = "" + count;
if ( this.doc.downloadCount > 0 )
{
this.progressDiv.style.opacity = "1";
}
else
{
this.progressDiv.style.opacity = "0";
}
}
}
else
{
this.progressDiv.style.opacity = "0";
}
}
//fire downloadsfinished event, if necessary
if ( this.doc.downloadCount <= 0 && this.doc.previousDownloadCount > 0 )
{
var evt;
if ( document.createEvent )
{
evt = document.createEvent( "Events" );
evt.initEvent( "downloadsfinished", true, true );
that.x3dElem.dispatchEvent( evt );
}
else if ( document.createEventObject )
{
evt = document.createEventObject();
// http://stackoverflow.com/questions/1874866/how-to-fire-onload-event-on-document-in-ie
that.x3dElem.fireEvent( "ondownloadsfinished", evt );
}
}
this.doc.previousDownloadCount = this.doc.downloadCount;
};
//------------------------------------------------------------------------------
x3dom.X3DCanvas.prototype.mainloop = function ( timestamp, xrFrame )
{
if ( this.doc && this.x3dElem.runtime )
{
this._watchForResize();
this.tick( timestamp, xrFrame );
if ( this.xrSession )
{
this.xrSession.requestAnimationFrame( this.mainloop );
}
else
{
window.requestAnimFrame( this.mainloop );
}
}
};
//------------------------------------------------------------------------------
/** Loads the given @p uri.
* @param uri can be a uri or an X3D node
* @param sceneElemPos
* @param settings properties
*/
x3dom.X3DCanvas.prototype.load = function ( uri, sceneElemPos, settings )
{
this.doc = new x3dom.X3DDocument( this.canvas, this.gl, settings );
this.doc.onload = () =>
{
this.mainloop = this.mainloop.bind( this );
this.checkForVRSupport();
if ( this.hasRuntime )
{
this.mainloop();
}
else
{
this.tick();
}
};
this.x3dElem.render = () =>
{
if ( this.hasRuntime )
{
this.doc.needRender = true;
}
else
{
this.doc.render( x3dCanvas.gl );
}
};
this.x3dElem.context = this.gl.ctx3d;
this.doc.onerror = function ()
{
alert( "Failed to load X3D document" );
};
this.doc.load( uri, sceneElemPos );
};
//------------------------------------------------------------------------------
x3dom.X3DCanvas.prototype.checkForVRSupport = function ()
{
if ( !navigator.xr )
{
return;
}
navigator.xr.isSessionSupported( "immersive-vr" ).then( ( isSupported ) =>
{
if ( isSupported )
{
this.vrDiv.style.display = "block";
}
} );
};
x3dom.X3DCanvas.prototype.enterVR = function ()
{
if ( this.xrSession )
{
return;
}
this.gl.ctx3d.makeXRCompatible().then( () =>
{
navigator.xr.requestSession( "immersive-vr" ).then( ( session ) =>
{
session.requestReferenceSpace( "local" ).then( ( space ) =>
{
const xrLayer = new XRWebGLLayer( session, this.gl.ctx3d );
session.updateRenderState( { baseLayer: xrLayer } );
this._oldCanvasWidth = this.canvas.width;
this._oldCanvasHeight = this.canvas.height;
this.canvas.width = xrLayer.framebufferWidth;
this.canvas.height = xrLayer.framebufferHeight;
this.gl.VRMode = 2;
this.xrReferenceSpace = space;
this.xrSession = session;
this.doc.needRender = true;
var mat_view = this.doc._viewarea.getViewMatrix();
var rotation = new x3dom.fields.Quaternion( 0, 0, 1, 0 );
rotation.normalize();
rotation.setValue( mat_view );
var translation = mat_view.e3();
const offsetTransform = new XRRigidTransform( {x: translation.x, y: translation.y, z: translation.z},
{x: rotation.x, y: rotation.y, z: rotation.z, w: rotation.w} );
this.xrReferenceSpace = this.xrReferenceSpace.getOffsetReferenceSpace( offsetTransform );
this.xrSession.addEventListener( "end", () =>
{
this.exitVR();
} );
session.requestAnimationFrame( this.mainloop );
} );
} );
} );
};
//------------------------------------------------------------------------------
x3dom.X3DCanvas.prototype.exitVR = function ()
{
if ( !this.xrSession )
{
return;
}
this.xrSession.end();
this.xrSession = undefined;
this.xrReferenceSpace = undefined;
this.canvas.width = this._oldCanvasWidth;
this.canvas.height = this._oldCanvasHeight;
this.gl.VRMode = 1;
this.doc.needRender = true;
window.requestAnimationFrame( this.mainloop );
};
//------------------------------------------------------------------------------
x3dom.X3DCanvas.prototype.getVRFrameData = function ( xrFrame )
{
if ( !xrFrame )
{
return;
}
const pose = xrFrame.getViewerPose( this.xrReferenceSpace );
if ( !pose )
{
return;
}
const vrFrameData = {
framebuffer : xrFrame.session.renderState.baseLayer.framebuffer,
controllers : {}
};
for ( const view of pose.views )
{
if ( view.eye === "left" )
{
vrFrameData.leftViewMatrix = view.transform.inverse.matrix;
vrFrameData.leftProjectionMatrix = view.projectionMatrix;
}
else if ( view.eye === "right" )
{
vrFrameData.rightViewMatrix = view.transform.inverse.matrix;
vrFrameData.rightProjectionMatrix = view.projectionMatrix;
}
}
for ( const inputSource of xrFrame.session.inputSources )
{
// Show the input source if it has a grip space
if ( inputSource.gripSpace )
{
const inputPose = xrFrame.getPose( inputSource.gripSpace, this.xrReferenceSpace );
if ( inputPose !== null && inputPose.transform !== null )
{
vrFrameData.controllers[ inputSource.handedness ] = {
gamepad : inputSource.gamepad,
type : inputSource.profiles[ 0 ],
pose : {
position : [
inputPose.transform.position.x,
inputPose.transform.position.y,
inputPose.transform.position.z
],
orientation : [
inputPose.transform.orientation.x,
inputPose.transform.orientation.y,
inputPose.transform.orientation.z,
inputPose.transform.orientation.w
]
}
};
}
}
}
return vrFrameData;
};
/**
* X3DOM JavaScript Library
* http://www.x3dom.org
*
* (C)2009 Fraunhofer IGD, Darmstadt, Germany
* Dual licensed under the MIT and GPL
*
* Based on code originally provided by
* Philip Taylor: http://philip.html5.org
*/
/**
* Input types - X3DOM allows either navigation or interaction.
* During each frame, only interaction of the current type is being processed, it is not possible to
* perform interaction (for instance, selecting or dragging objects) and navigation at the same time
*/
x3dom.InputTypes = {
NAVIGATION : 1,
INTERACTION : 2
};
/**
* Viewarea Constructor.
*
* @class represents a view area
* @param {x3dom.X3DDocument} document - the target X3DDocument
* @param {Object} scene - the scene
*/
x3dom.Viewarea = function ( document, scene )
{
this._doc = document; // x3ddocument
this._scene = scene; // FIXME: updates ?!
document._nodeBag.viewarea.push( this );
/**
* picking informations containing
* pickingpos, pickNorm, pickObj, firstObj, lastObj, lastClickObj, shadowObjId
* @var {Object} _pickingInfo
* @memberof x3dom.Viewarea
* @instance
* @protected
*/
this._pickingInfo = {
pickPos : new x3dom.fields.SFVec3f( 0, 0, 0 ),
pickNorm : new x3dom.fields.SFVec3f( 0, 0, 1 ),
pickObj : null,
firstObj : null,
lastObj : null,
lastClickObj : null,
shadowObjectId : -1
};
this._currentInputType = x3dom.InputTypes.NAVIGATION;
/**
* rotation matrix
* @var {x3dom.fields.SFMatrix4f} _rotMat
* @memberof x3dom.Viewarea
* @instance
* @protected
*/
this._rotMat = x3dom.fields.SFMatrix4f.identity();
/**
* translation matrix
* @var {x3dom.fields.SFMatrix4f} _transMat
* @memberof x3dom.Viewarea
* @instance
* @protected
*/
this._transMat = x3dom.fields.SFMatrix4f.identity();
/**
* movement vector
* @var {x3dom.fields.SFVec3f} _movement
* @memberof x3dom.Viewarea
* @instance
* @protected
*/
this._movement = new x3dom.fields.SFVec3f( 0, 0, 0 );
/**
* flag to signal a needed NavigationMatrixUpdate
* @var {Boolean} _needNavigationMatrixUpdate
* @memberof x3dom.Viewarea
* @instance
* @protected
*/
this._needNavigationMatrixUpdate = true;
/**
* time passed since last update
* @var {Number} _deltaT
* @memberof x3dom.Viewarea
* @instance
* @protected
*/
this._deltaT = 0;
this._flyMat = null;
this._pitch = 0;
this._yaw = 0;
/**
* eye position of the view area
* @var {x3dom.fields.SFVec3f} _eyePos
* @memberof x3dom.Viewarea
* @instance
* @protected
*/
this._eyePos = new x3dom.fields.SFVec3f( 0, 0, 0 );
/**
* width of the view area
* @var {Number} _width
* @memberof x3dom.Viewarea
* @instance
* @protected
*/
this._width = 400;
/**
* height of the view area
* @var {Number} _height
* @memberof x3dom.Viewarea
* @instance
* @protected
*/
this._height = 300;
this._dx = 0;
this._dy = 0;
this._lastX = -1;
this._lastY = -1;
this._pressX = -1;
this._pressY = -1;
this._lastButton = 0;
this._points = 0; // old render mode flag (but think of better name!)
this._numRenderedNodes = 0;
this._pick = new x3dom.fields.SFVec3f( 0, 0, 0 );
this._pickNorm = new x3dom.fields.SFVec3f( 0, 0, 1 );
this._isAnimating = false;
this._isMoving = false;
this._lastTS = 0;
this._mixer = new x3dom.MatrixMixer();
this._interpolator = new x3dom.FieldInterpolator();
this._animationStateChanged = false;
this.vrFrameData = null;
this.gamepads = null;
this.vrLeftViewMatrix = new x3dom.fields.SFMatrix4f();
this.vrRightViewMatrix = new x3dom.fields.SFMatrix4f();
this.vrLeftProjMatrix = new x3dom.fields.SFMatrix4f();
this.vrRightProjMatrix = new x3dom.fields.SFMatrix4f();
this.vrControllerManager = new x3dom.VRControllerManager( this._doc );
this._inverseDevicePixelRatio = 1.0 / window.devicePixelRatio;
this.arc = null;
};
x3dom.Viewarea.prototype.setVRFrameData = function ( gl, vrFrameData )
{
this.vrFrameData = vrFrameData;
if ( this.vrFrameData )
{
this.vrLeftViewMatrix.setFromArray( this.vrFrameData.leftViewMatrix );
this.vrRightViewMatrix.setFromArray( this.vrFrameData.rightViewMatrix );
this.vrLeftProjMatrix.setFromArray( this.vrFrameData.leftProjectionMatrix );
this.vrRightProjMatrix.setFromArray( this.vrFrameData.rightProjectionMatrix );
}
};
x3dom.Viewarea.prototype.updateGamepads = function ( vrFrameData )
{
this.vrControllerManager.update( this, vrFrameData );
};
/**
* Method gets called every frame with the current timestamp
*
* @param {Number} timeStamp - current time stamp
* @returns {Boolean} view area animation state
*/
x3dom.Viewarea.prototype.tick = function ( timeStamp )
{
var needMixAnim = false;
var env = this._scene.getEnvironment();
if ( env._vf.enableARC && this.arc == null )
{
this.arc = new x3dom.arc.AdaptiveRenderControl( this._scene );
}
if ( this._mixer.isActive() )
{
if ( this._mixer._isVPtarget )
{
//mixer target may have changed due to current transform
//need to refresh from scratch
var vp = this._scene.getViewpoint();
vp.resetView();
var target = vp.getViewMatrix().mult( vp.getCurrentTransform().inverse() );
this._mixer.setEndMatrix( target );
}
var mat = this._mixer.mix( timeStamp );
this._scene.getViewpoint().setView( mat );
}
if ( this._interpolator.isActive() )
{
var value = this._interpolator.interpolate( timeStamp );
this._scene.getViewpoint().setZoom( value );
}
var needNavAnim = this.navigateTo( timeStamp );
var lastIsAnimating = this._isAnimating;
this._lastTS = timeStamp;
this._isAnimating = ( this._mixer.isMixing || this._interpolator.isInterpolating || needNavAnim );
if ( this._isAnimating != lastIsAnimating )
{
this._animationStateChanged = true;
}
else
{
this._animationStateChanged = false;
}
if ( this.arc != null )
{
this.arc.update( this.isMovingOrAnimating() ? 1 : 0, this._doc._x3dElem.runtime.getFPS() );
}
return ( this._isAnimating || lastIsAnimating );
};
/**
* Returns moving state of view are
*
* @returns {Boolean} moving state of view area
*/
x3dom.Viewarea.prototype.isMoving = function ()
{
return this._isMoving;
};
/**
* Returns animation state of view area
*
* @returns {Boolean} animation state of view area
*/
x3dom.Viewarea.prototype.isAnimating = function ()
{
return this._isAnimating;
};
/**
* Returns if the animation state has changed since the last update
*
* @returns {Boolean} animation state of view area
*/
x3dom.Viewarea.prototype.hasAnimationStateChanged = function ()
{
return this._animationStateChanged;
};
/**
* is view area moving or animating
*
* @returns {Boolean} view area moving or animating state
*/
x3dom.Viewarea.prototype.isMovingOrAnimating = function ()
{
return ( this._isMoving || this._isAnimating );
};
/**
* triggers view area to move to something by passing the timestamp
* returning a flag if the view area needs a navigation animation
*
* @returns {Boolean} flag if the view area need a navigation state
*/
x3dom.Viewarea.prototype.navigateTo = function ( timeStamp )
{
var navi = this._scene.getNavigationInfo();
return navi._impl.navigateTo( this, timeStamp );
};
/**
* Move Forward
*
*/
x3dom.Viewarea.prototype.moveFwd = function ()
{
var navi = this._scene.getNavigationInfo();
navi._impl.moveForward( this );
};
/**
* Move Backwards
*
*/
x3dom.Viewarea.prototype.moveBwd = function ()
{
var navi = this._scene.getNavigationInfo();
navi._impl.moveBackwards( this );
};
/**
* Strafe Right
*
*/
x3dom.Viewarea.prototype.strafeRight = function ()
{
var navi = this._scene.getNavigationInfo();
navi._impl.strafeRight( this );
};
/**
* Strafe Left
*
*/
x3dom.Viewarea.prototype.strafeLeft = function ()
{
var navi = this._scene.getNavigationInfo();
navi._impl.strafeLeft( this );
};
/**
* Animate To
*
* @param target
* @param prev
* @param dur
*/
x3dom.Viewarea.prototype.animateTo = function ( target, prev, dur )
{
var navi = this._scene.getNavigationInfo();
navi._impl.animateTo( this, target, prev, dur );
};
/**
* Ortho Animate To
*
* @param target
* @param prev
* @param duration
*/
x3dom.Viewarea.prototype.orthoAnimateTo = function ( target, prev, duration )
{
var navi = this._scene.getNavigationInfo();
navi._impl.orthoAnimateTo( this, target, prev, duration );
};
/**
* Zoom
*
* @param zoomAmount
*/
x3dom.Viewarea.prototype.zoom = function ( zoomAmount )
{
var navi = this._scene.getNavigationInfo();
navi._impl.zoom( this, zoomAmount );
};
/**
* Get Lights
*
* @returns {Array}
*/
x3dom.Viewarea.prototype.getLights = function ()
{
var enabledLights = [];
for ( var i = 0; i < this._doc._nodeBag.lights.length; i++ )
{
if ( this._doc._nodeBag.lights[ i ]._vf.on == true )
{
enabledLights.push( this._doc._nodeBag.lights[ i ] );
}
}
return enabledLights;
};
/**
* Get Lights Shadow
*
* @returns {boolean}
*/
x3dom.Viewarea.prototype.getLightsShadow = function ()
{
var lights = this._doc._nodeBag.lights;
for ( var l = 0; l < lights.length; l++ )
{
if ( lights[ l ]._vf.shadowIntensity > 0.0 && lights[ l ]._vf.on )
{
return true;
}
}
return false;
};
/**
* Check if there is a PhysicalEnvironmentLight
*
* @returns {boolean}
*/
x3dom.Viewarea.prototype.hasPhysicalEnvironmentLight = function ()
{
var light;
for ( var i = 0; i < this._doc._nodeBag.lights.length; i++ )
{
var light = this._doc._nodeBag.lights[ i ];
if ( x3dom.isa( light, x3dom.nodeTypes.PhysicalEnvironmentLight ) &&
light._vf.on )
{
return true;
}
}
return false;
};
/**
* Update Special Navigation
*
* @param viewpoint
* @param mat_viewpoint
*/
x3dom.Viewarea.prototype.updateSpecialNavigation = function ( viewpoint, mat_viewpoint )
{
var navi = this._scene.getNavigationInfo();
var navType = navi.getType();
// helicopter mode needs to manipulate view matrix specially
if ( navType == "helicopter" && !navi._heliUpdated )
{
var typeParams = navi.getTypeParams();
var theta = typeParams[ 0 ];
var currViewMat = viewpoint.getViewMatrix().mult( mat_viewpoint.inverse() ).inverse();
this._from = currViewMat.e3();
this._at = this._from.subtract( currViewMat.e2() );
this._up = new x3dom.fields.SFVec3f( 0, 1, 0 );
this._from.y = typeParams[ 1 ];
this._at.y = this._from.y;
var sv = currViewMat.e0();
var q = x3dom.fields.Quaternion.axisAngle( sv, theta );
var temp = q.toMatrix();
var fin = x3dom.fields.SFMatrix4f.translation( this._from );
fin = fin.mult( temp );
temp = x3dom.fields.SFMatrix4f.translation( this._from.negate() );
fin = fin.mult( temp );
this._at = fin.multMatrixPnt( this._at );
this._flyMat = x3dom.fields.SFMatrix4f.lookAt( this._from, this._at, this._up );
this._scene.getViewpoint().setView( this._flyMat.inverse() );
navi._heliUpdated = true;
}
};
/**
* Get the view areas view point matrix
*
* @returns {x3dom.fields.SFMatrix4f} view areas view point matrix
*/
x3dom.Viewarea.prototype.getViewpointMatrix = function ()
{
var viewpoint = this._scene.getViewpoint();
var mat_viewpoint = viewpoint.getCurrentTransform();
this.updateSpecialNavigation( viewpoint, mat_viewpoint );
return viewpoint.getViewMatrix().mult( mat_viewpoint.inverse() );
};
/**
* Get the view areas view matrix
*
* @returns {x3dom.fields.SFMatrix4f} view areas view matrix
*/
x3dom.Viewarea.prototype.getViewMatrix = function ()
{
if ( this.vrFrameData )
{
return this.vrLeftViewMatrix;
}
else
{
return this.getViewpointMatrix().mult( this._transMat ).mult( this._rotMat );
}
};
x3dom.Viewarea.prototype.getViewMatrices = function ()
{
if ( this.vrFrameData )
{
return [ this.vrLeftViewMatrix,
this.vrRightViewMatrix ];
}
else
{
var viewMatrix = this.getViewpointMatrix().mult( this._transMat ).mult( this._rotMat );
return [ viewMatrix, viewMatrix ];
}
};
/**
* Get Light Matrix
*
* @param lights
* @returns {*}
*/
x3dom.Viewarea.prototype.getLightMatrix = function ( lights )
{
lights = lights || this._doc._nodeBag.lights;
var i,
n = lights.length;
if ( n > 0 )
{
var vol = this._scene.getVolume();
if ( vol.isValid() )
{
var min = x3dom.fields.SFVec3f.MAX();
var max = x3dom.fields.SFVec3f.MIN();
vol.getBounds( min, max );
var l_arr = [];
var viewpoint = this._scene.getViewpoint();
var fov = viewpoint.getFieldOfView();
var dia = max.subtract( min );
var dist1 = ( dia.y / 2.0 ) / Math.tan( fov / 2.0 ) + ( dia.z / 2.0 );
var dist2 = ( dia.x / 2.0 ) / Math.tan( fov / 2.0 ) + ( dia.z / 2.0 );
dia = min.add( dia.multiply( 0.5 ) );
for ( i = 0; i < n; i++ )
{
var light = lights[ i ];
if ( x3dom.isa( light, x3dom.nodeTypes.PointLight ) )
{
var wcLoc = light.getCurrentTransform().multMatrixPnt( light._vf.location );
dia = dia.subtract( wcLoc ).normalize();
}
else
{
var dir = light.getCurrentTransform().multMatrixVec( light._vf.direction );
dir = dir.normalize().negate();
dia = dia.add( dir.multiply( 1.2 * ( dist1 > dist2 ? dist1 : dist2 ) ) );
}
l_arr.push( light.getViewMatrix( dia ) );
}
return l_arr;
}
}
// TODO: was only for testing but now expected
return Array( n || 1 ).fill( this.getViewMatrix() );
};
/**
* Get WC to LC Matrix
*
* @param lMat
* @returns {x3dom.fields.SFMatrix4f|*|void}
*/
x3dom.Viewarea.prototype.getWCtoLCMatrix = function ( lMat )
{
var proj = this.getProjectionMatrix();
var view;
if ( arguments.length === 0 )
{
view = this.getLightMatrix()[ 0 ];
}
else
{
view = lMat;
}
return proj.mult( view );
};
/**
* Get six WCtoLCMatrices for point light
*
* @param {x3dom.fields.SFMatrix4f} view - the view matrix
* @param {x3dom.nodeTypes.X3DNode} lightNode - the light node
* @param {x3dom.fields.SFMatrix4f} mat_proj - the projection matrix
* @returns {Array} six WCtoLCMatrices
*/
x3dom.Viewarea.prototype.getWCtoLCMatricesPointLight = function ( view, lightNode, mat_proj )
{
var zNear = lightNode._vf.zNear;
var zFar = lightNode._vf.zFar;
var proj = this.getLightProjectionMatrix( view, zNear, zFar, false, mat_proj );
// set projection matrix to 90 degrees FOV (vertical and horizontal)
proj._00 = 1;
proj._11 = 1;
var matrices = [];
// create six matrices to cover all directions of point light
matrices[ 0 ] = proj.mult( view );
var rotationMatrix;
// y-rotation
for ( var i = 1; i <= 3; i++ )
{
rotationMatrix = x3dom.fields.SFMatrix4f.rotationY( i * Math.PI / 2 );
matrices[ i ] = proj.mult( rotationMatrix.mult( view ) );
}
//x-rotation
rotationMatrix = x3dom.fields.SFMatrix4f.rotationX( Math.PI / 2 );
matrices[ 4 ] = proj.mult( rotationMatrix.mult( view ) );
rotationMatrix = x3dom.fields.SFMatrix4f.rotationX( 3 * Math.PI / 2 );
matrices[ 5 ] = proj.mult( rotationMatrix.mult( view ) );
return matrices;
};
/**
* Get WCToLCMatrices for cascaded light
*
* @param view
* @param lightNode
* @param mat_proj
* @returns {Array}
*/
x3dom.Viewarea.prototype.getWCtoLCMatricesCascaded = function ( view, lightNode, mat_proj )
{
var numCascades = Math.max( 1, Math.min( lightNode._vf.shadowCascades, 6 ) );
var splitFactor = Math.max( 0, Math.min( lightNode._vf.shadowSplitFactor, 1 ) );
var splitOffset = Math.max( 0, Math.min( lightNode._vf.shadowSplitOffset, 1 ) );
var isSpotLight = x3dom.isa( lightNode, x3dom.nodeTypes.SpotLight );
var zNear = lightNode._vf.zNear;
var zFar = lightNode._vf.zFar;
var proj = this.getLightProjectionMatrix( view, zNear, zFar, true, mat_proj );
if ( isSpotLight )
{
//set FOV to 90 degrees
proj._00 = 1;
proj._11 = 1;
}
// get view projection matrix
var viewProj = proj.mult( view );
var matrices = [];
if ( numCascades == 1 )
{
//return if only one cascade
matrices[ 0 ] = viewProj;
return matrices;
}
// compute split positions of view frustum
var cascadeSplits = this.getShadowSplitDepths( numCascades, splitFactor, splitOffset, true, mat_proj );
// calculate fitting matrices and multiply with view projection
for ( var i = 0; i < numCascades; i++ )
{
var fittingMat = this.getLightFittingMatrix( viewProj, cascadeSplits[ i ], cascadeSplits[ i + 1 ], mat_proj );
matrices[ i ] = fittingMat.mult( viewProj );
}
return matrices;
};
/**
* Get Light Projection Matrix
*
* @param lMat
* @param zNear
* @param zFar
* @param highPrecision
* @param mat_proj
* @returns {*}
*/
x3dom.Viewarea.prototype.getLightProjectionMatrix = function ( lMat, zNear, zFar, highPrecision, mat_proj )
{
var proj = x3dom.fields.SFMatrix4f.copy( mat_proj );
if ( !highPrecision || zNear > 0 || zFar > 0 )
{
//replace near and far plane of projection matrix
//by values adapted to the light position
var lightPos = lMat.inverse().e3();
var nearScale = 0.8;
var farScale = 1.2;
var min = x3dom.fields.SFVec3f.copy( this._scene._lastMin );
var max = x3dom.fields.SFVec3f.copy( this._scene._lastMax );
var dia = max.subtract( min );
var sRad = dia.length() / 2;
var sCenter = min.add( dia.multiply( 0.5 ) );
var vDist = ( lightPos.subtract( sCenter ) ).length();
var near,
far;
if ( sRad )
{
if ( vDist > sRad )
{near = ( vDist - sRad ) * nearScale;}
else
{near = 1;}
far = ( vDist + sRad ) * farScale;
}
if ( zNear > 0 ) {near = zNear;}
if ( zFar > 0 ) {far = zFar;}
proj._22 = -( far + near ) / ( far - near );
proj._23 = -2.0 * far * near / ( far - near );
return proj;
}
else
{
//should be more accurate, but also more expensive
var cropMatrix = this.getLightCropMatrix( proj.mult( lMat ) );
return cropMatrix.mult( proj );
}
};
/**
* Get Projection Matrix
*
* @returns {*}
*/
x3dom.Viewarea.prototype.getProjectionMatrix = function ()
{
if ( this.vrFrameData )
{
return this.vrLeftProjMatrix;
}
else
{
var viewpoint = this._scene.getViewpoint();
return viewpoint.getProjectionMatrix( this._width / this._height );
}
};
/**
* Get Projection Matrices for both eyes
*
* @returns {*}
*/
x3dom.Viewarea.prototype.getProjectionMatrices = function ()
{
if ( this.vrFrameData )
{
return [ this.vrLeftProjMatrix, this.vrRightProjMatrix ];
}
else
{
var viewpoint = this._scene.getViewpoint();
var projectionMatrix = viewpoint.getProjectionMatrix( this._width / this._height );
return [ projectionMatrix, projectionMatrix ];
}
};
/**
* Get the view frustum for a given clipping matrix
*
* @param {x3dom.fields.SFMatrix4f} clipMat - the clipping matrix
* @returns {x3dom.fields.FrustumVolume} the resulting view frustum
*/
x3dom.Viewarea.prototype.getViewfrustum = function ( clipMat )
{
var env = this._scene.getEnvironment();
if ( env._vf.frustumCulling == true )
{
if ( arguments.length == 0 )
{
var proj = this.getProjectionMatrix();
var view = this.getViewMatrix();
return new x3dom.fields.FrustumVolume( proj.mult( view ) );
}
else
{
return new x3dom.fields.FrustumVolume( clipMat );
}
}
return null;
};
/**
* Get the world coordinates to clipping coordinates matrix by multiplying the projection and view matrices
*
* @returns {x3dom.fields.SFMatrix4f} world coordinates to clipping coordinates matrix
*/
x3dom.Viewarea.prototype.getWCtoCCMatrix = function ()
{
var view = this.getViewMatrix();
var proj = this.getProjectionMatrix();
return proj.mult( view );
};
/**
* Get the clipping coordinates to world coordinates matrix by multiplying the projection and view matrices
*
* @returns {x3dom.fields.SFMatrix4f} clipping coordinates to world coordinates matrix
*/
x3dom.Viewarea.prototype.getCCtoWCMatrix = function ()
{
var mat = this.getWCtoCCMatrix();
return mat.inverse();
};
/**
* Calculate View Ray
*
* @param x
* @param y
* @param mat
* @returns {x3dom.fields.Ray}
*/
x3dom.Viewarea.prototype.calcViewRay = function ( x, y, mat )
{
var cctowc = mat ? mat : this.getCCtoWCMatrix();
var rx = x / ( this._width - 1.0 ) * 2.0 - 1.0;
var ry = ( this._height - 1.0 - y ) / ( this._height - 1.0 ) * 2.0 - 1.0;
var from = cctowc.multFullMatrixPnt( new x3dom.fields.SFVec3f( rx, ry, -1 ) );
var at = cctowc.multFullMatrixPnt( new x3dom.fields.SFVec3f( rx, ry, 1 ) );
var dir = at.subtract( from );
return new x3dom.fields.Ray( from, dir );
};
/**
* Show All
*
* @param axis
* @param updateCenterOfRotation
*/
x3dom.Viewarea.prototype.showAll = function ( axis, updateCenterOfRotation )
{
if ( axis === undefined )
{axis = "negZ";}
if ( updateCenterOfRotation === undefined )
{
updateCenterOfRotation = false;
}
var scene = this._scene;
scene.updateVolume();
var min = x3dom.fields.SFVec3f.copy( scene._lastMin );
var max = x3dom.fields.SFVec3f.copy( scene._lastMax );
var x = "x",
y = "y",
z = "z";
var sign = 1;
var to,
from = new x3dom.fields.SFVec3f( 0, 0, -1 );
switch ( axis )
{
case "posX":
sign = -1;
case "negX":
z = "x";
x = "y";
y = "z";
to = new x3dom.fields.SFVec3f( sign, 0, 0 );
break;
case "posY":
sign = -1;
case "negY":
z = "y";
x = "z";
y = "x";
to = new x3dom.fields.SFVec3f( 0, sign, 0 );
break;
case "posZ":
sign = -1;
case "negZ":
default:
to = new x3dom.fields.SFVec3f( 0, 0, -sign );
break;
}
var viewpoint = scene.getViewpoint();
var fov = viewpoint.getFieldOfView();
var isOrtho = x3dom.isa( viewpoint, x3dom.nodeTypes.OrthoViewpoint );
var dia = max.subtract( min );
var dia2 = dia.multiply( 0.5 );
var center = min.add( dia2 );
if ( updateCenterOfRotation )
{
viewpoint.setCenterOfRotation( center );
}
var aspect = Math.min( this._width / this._height, 1 );
var diaz2 = dia[ z ] / 2.0,
tanfov2 = Math.tan( fov / 2.0 );
var dist1 = ( dia[ y ] / 2.0 ) / tanfov2 + diaz2;
var dist2 = ( dia[ x ] / 2.0 ) / tanfov2 + diaz2;
dia = min.add( dia.multiply( 0.5 ) );
if ( isOrtho )
{
dia[ z ] += sign * ( dist1 > dist2 ? dist1 : dist2 ) * 3.01;
}
else
{
dia[ z ] += sign * ( dist1 > dist2 ? dist1 : dist2 ) * 1.01;
}
dia[ z ] /= aspect;
var quat = x3dom.fields.Quaternion.rotateFromTo( from, to );
var viewmat = quat.toMatrix();
viewmat = viewmat.mult( x3dom.fields.SFMatrix4f.translation( dia.negate() ) );
if ( isOrtho )
{
this.orthoAnimateTo( dist1, Math.abs( viewpoint._fieldOfView[ 0 ] ) );
this.animateTo( viewmat, viewpoint );
}
else
{
this.animateTo( viewmat, viewpoint );
}
};
/**
* Fit View
*
* @param min
* @param max
* @param updateCenterOfRotation
*/
x3dom.Viewarea.prototype.fit = function ( min, max, updateCenterOfRotation )
{
var viewpoint = this._scene.getViewpoint();
var viewmat = this.getFitViewMatrix( min, max, viewpoint, updateCenterOfRotation );
if ( x3dom.isa( viewpoint, x3dom.nodeTypes.OrthoViewpoint ) )
{
this.orthoAnimateTo( dist / 2.01, Math.abs( viewpoint._fieldOfView[ 0 ] ) );
this.animateTo( viewmat, viewpoint );
}
else
{
this.animateTo( viewmat, viewpoint );
}
};
/**
* get fitViewMatrix and optionally set cor
*
* @param min
* @param max
* @param viewpoint
* @param updateCenterOfRotation
* @returns viewmatrix to fit scene
*/
x3dom.Viewarea.prototype.getFitViewMatrix = function ( min, max, viewpoint, updateCenterOfRotation )
{
if ( updateCenterOfRotation === undefined )
{
updateCenterOfRotation = true;
}
var dia2 = max.subtract( min ).multiply( 0.5 ); // half diameter
var center = min.add( dia2 ); // center in wc
var bsr = dia2.length(); // bounding sphere radius
var fov = viewpoint.getFieldOfView();
var viewmat = x3dom.fields.SFMatrix4f.copy( this.getViewMatrix() );
var rightDir = new x3dom.fields.SFVec3f( viewmat._00, viewmat._01, viewmat._02 );
var upDir = new x3dom.fields.SFVec3f( viewmat._10, viewmat._11, viewmat._12 );
var viewDir = new x3dom.fields.SFVec3f( viewmat._20, viewmat._21, viewmat._22 );
var aspect = Math.min( this._width / this._height, 1 );
var tanfov2 = Math.tan( fov / 2.0 );
var dist = bsr / tanfov2 / aspect;
var eyePos = center.add( viewDir.multiply( dist ) );
viewmat._03 = -rightDir.dot( eyePos );
viewmat._13 = -upDir.dot( eyePos );
viewmat._23 = -viewDir.dot( eyePos );
if ( updateCenterOfRotation )
{
viewpoint.setCenterOfRotation( center );
}
return viewmat;
};
/**
* Reset View
*
*/
x3dom.Viewarea.prototype.resetView = function ()
{
var navi = this._scene.getNavigationInfo();
navi._impl.resetView( this );
};
/**
* Reset Navigation Helpers
*
*/
x3dom.Viewarea.prototype.resetNavHelpers = function ()
{
this._rotMat = x3dom.fields.SFMatrix4f.identity();
this._transMat = x3dom.fields.SFMatrix4f.identity();
this._movement = new x3dom.fields.SFVec3f( 0, 0, 0 );
this._needNavigationMatrixUpdate = true;
};
/**
* Upright View
*
*/
x3dom.Viewarea.prototype.uprightView = function ()
{
var mat = this.getViewMatrix().inverse();
var from = mat.e3();
var at = from.subtract( mat.e2() );
var up = new x3dom.fields.SFVec3f( 0, 1, 0 );
var s = mat.e2().cross( up ).normalize();
var v = s.cross( up ).normalize();
at = from.add( v );
mat = x3dom.fields.SFMatrix4f.lookAt( from, at, up );
mat = mat.inverse();
this.animateTo( mat, this._scene.getViewpoint() );
};
/**
* Call Event Handler
*
* @param node
* @param eventType
* @param event
* @returns {*}
*/
x3dom.Viewarea.prototype.callEvtHandler = function ( node, eventType, event )
{
if ( !node || !node._xmlNode )
{return null;}
try
{
var xmlNode = node._xmlNode;
var attrib = xmlNode[ eventType ];
if ( typeof( attrib ) === "function" )
{
attrib.call( xmlNode, event );
}
else if ( xmlNode.hasAttribute( eventType ) )
{
var funcStr = xmlNode.getAttribute( eventType );
var func = new Function( "event", funcStr );
func.call( xmlNode, event );
}
var list = node._listeners[ event.type ];
if ( list )
{
for ( var it = 0; it < list.length; it++ )
{
list[ it ].call( xmlNode, event );
}
}
}
catch ( e )
{
x3dom.debug.logException( e );
}
return event.cancelBubble;
};
/**
* Check Events
*
* @param obj
* @param x
* @param y
* @param buttonState
* @param eventType
* @returns {boolean}
*/
x3dom.Viewarea.prototype.checkEvents = function ( obj, x, y, buttonState, eventType )
{
var that = this;
var needRecurse = true;
var childNode,
i,
n;
var target = ( obj && obj._xmlNode ) ? obj._xmlNode : {};
var affectedPointingSensorsList = this._doc._nodeBag.affectedPointingSensors;
var event = {
viewarea : that,
target : target,
type : eventType.substr( 2, eventType.length - 2 ),
button : buttonState,
layerX : x * this._inverseDevicePixelRatio,
layerY : y * this._inverseDevicePixelRatio,
worldX : that._pick.x,
worldY : that._pick.y,
worldZ : that._pick.z,
normalX : that._pickNorm.x,
normalY : that._pickNorm.y,
normalZ : that._pickNorm.z,
hitPnt : that._pick.toGL(), // for convenience
hitObject : target, // deprecated, remove!
shadowObjectId : that._pickingInfo.shadowObjectId,
cancelBubble : false,
stopPropagation : function () { this.cancelBubble = true; },
preventDefault : function () { this.cancelBubble = true; }
};
try
{
var anObj = obj;
if ( anObj && anObj._xmlNode && anObj._cf.geometry &&
!anObj._xmlNode[ eventType ] &&
!anObj._xmlNode.hasAttribute( eventType ) &&
!anObj._listeners[ event.type ] )
{
anObj = anObj._cf.geometry.node;
}
if ( anObj && that.callEvtHandler( anObj, eventType, event ) === true )
{
needRecurse = false;
}
}
catch ( e )
{
x3dom.debug.logException( e );
}
var recurse = function ( obj )
{
obj._parentNodes.forEach( function ( node )
{
if ( node._xmlNode && ( node._xmlNode[ eventType ] ||
node._xmlNode.hasAttribute( eventType ) ||
node._listeners[ event.type ] ) )
{
if ( that.callEvtHandler( node, eventType, event ) === true )
{
needRecurse = false;
}
}
// find the lowest pointing device sensors in the hierarchy that might be affected
// (note that, for X3DTouchSensors, 'affected' does not necessarily mean 'activated')
if ( buttonState == 0 && affectedPointingSensorsList.length == 0 &&
( eventType == "onmousemove" || eventType == "onmouseover" || eventType == "onmouseout" ) )
{
n = node._childNodes.length;
for ( i = 0; i < n; ++i )
{
childNode = node._childNodes[ i ];
if ( x3dom.isa( childNode, x3dom.nodeTypes.X3DPointingDeviceSensorNode ) &&
childNode._vf.enabled &&
affectedPointingSensorsList.indexOf( childNode ) == -1 )
{
affectedPointingSensorsList.push( childNode );
}
}
}
if ( x3dom.isa( node, x3dom.nodeTypes.Anchor ) && eventType === "onclick" )
{
node.handleTouch();
needRecurse = false;
}
else if ( needRecurse )
{
recurse( node );
}
} );
};
if ( needRecurse && obj )
{
recurse( obj );
}
return needRecurse;
};
/**
* Notifies all pointing device sensors that are currently affected by mouse events, if any, about the given event
*
* @param {DOMEvent} event - a mouse event, enriched by X3DOM-specific members
*/
x3dom.Viewarea.prototype._notifyAffectedPointingSensors = function ( event )
{
var funcDict = {
"mousedown" : "pointerPressedOverSibling",
"mousemove" : "pointerMoved",
"mouseover" : "pointerMovedOver",
"mouseout" : "pointerMovedOut"
};
var func = funcDict[ event.type ];
var affectedPointingSensorsList = this._doc._nodeBag.affectedPointingSensors;
var i,
n = affectedPointingSensorsList.length;
if ( n > 0 && func !== undefined )
{
for ( i = 0; i < n; i++ )
{affectedPointingSensorsList[ i ][ func ]( event );}
}
};
/**
* Initialise Mouse State
*
*/
x3dom.Viewarea.prototype.initMouseState = function ()
{
this._deltaT = 0;
this._dx = 0;
this._dy = 0;
this._lastX = -1;
this._lastY = -1;
this._pressX = -1;
this._pressY = -1;
this._lastButton = 0;
this._isMoving = false;
this._needNavigationMatrixUpdate = true;
};
/**
* On Mouse Press
*
* @param x
* @param y
* @param buttonState
*/
x3dom.Viewarea.prototype.onMousePress = function ( x, y, buttonState )
{
this._needNavigationMatrixUpdate = true;
// simulate move first, in case mouse not moved after last click
this.prepareEvents( x, y, 0, "onmouseover" );
// touchsensor isActive is invoked now since in affectedPointingSensorList
this.prepareEvents( x, y, buttonState, "onmousedown" );
this._pickingInfo.lastClickObj = this._pickingInfo.pickObj;
this._pickingInfo.firstObj = this._pickingInfo.pickObj;
this._dx = 0;
this._dy = 0;
this._lastX = x;
this._lastY = y;
this._pressX = x;
this._pressY = y;
this._lastButton = buttonState;
this._isMoving = false;
if ( this._currentInputType == x3dom.InputTypes.NAVIGATION )
{
var navi = this._scene.getNavigationInfo();
navi._impl.onMousePress( this, x, y, buttonState );
}
};
/**
* On Mouse Release
*
* @param x
* @param y
* @param buttonState
* @param prevButton
*/
x3dom.Viewarea.prototype.onMouseRelease = function ( x, y, buttonState, prevButton )
{
var i,
dir;
// if the mouse is released, reset the list of currently affected pointing sensors
var affectedPointingSensorsList = this._doc._nodeBag.affectedPointingSensors;
for ( i = 0; i < affectedPointingSensorsList.length; ++i )
{
affectedPointingSensorsList[ i ].pointerReleased();
}
this._doc._nodeBag.affectedPointingSensors = [];
var tDist = 3.0; // distance modifier for lookat, could be param
var navi = this._scene.getNavigationInfo();
var navType = navi.getType();
var pickMode = this._scene._vf.pickMode.toLowerCase();
if ( pickMode !== "box" )
{
this.prepareEvents( x, y, prevButton, "onmouseup" );
// click means that mousedown _and_ mouseup were detected on same element
if ( this._pickingInfo.pickObj &&
this._pickingInfo.pickObj === this._pickingInfo.lastClickObj )
{
this.prepareEvents( x, y, prevButton, "onclick" );
}
else if ( !this._pickingInfo.pickObj && !this._pickingInfo.lastClickObj &&
!this._pickingInfo.firstObj ) // press and release outside object
{
var eventType = "backgroundClicked";
try
{
if ( this._scene._xmlNode &&
( this._scene._xmlNode[ "on" + eventType ] ||
this._scene._xmlNode.hasAttribute( "on" + eventType ) ||
this._scene._listeners[ eventType ] ) )
{
var event = {
target : this._scene._xmlNode, type : eventType,
button : prevButton,
layerX : x * this._inverseDevicePixelRatio,
layerY : y * this._inverseDevicePixelRatio,
cancelBubble : false,
stopPropagation : function () { this.cancelBubble = true; },
preventDefault : function () { this.cancelBubble = true; }
};
this._scene.callEvtHandler( ( "on" + eventType ), event );
}
}
catch ( e )
{
x3dom.debug.logException( "backgroundClicked: " + e );
}
}
}
else
{
var t0 = Date.now();
var line = this.calcViewRay( x, y );
var isect = this._scene.doIntersect( line );
var obj = line.hitObject;
if ( isect && obj )
{
this._pick.setValues( line.hitPoint );
this.checkEvents( obj, x, y, buttonState, "onclick" );
x3dom.debug.logInfo( "Hit '" + obj._xmlNode.localName + "/ " +
obj._DEF + "' at dist=" + line.dist.toFixed( 4 ) );
if ( pickMode === "color" )
{
x3dom.debug.logInfo( "Ray hit color " + this._pick );
}
else if ( pickMode === "idbufid" || pickMode === "texcoord" )
{
x3dom.debug.logInfo( "Ray hit data " + this._pick );
}
else
{ // idbuf idbuf24 box
x3dom.debug.logInfo( "Ray hit at position "
+ this._pick.x.toFixed( 4 ) + " "
+ this._pick.y.toFixed( 4 ) + " "
+ this._pick.z.toFixed( 4 )
);
}
}
var t1 = Date.now() - t0;
x3dom.debug.logInfo( "Picking time (box): " + t1 + "ms" );
if ( !isect )
{
dir = this.getViewMatrix().e2().negate();
var u = dir.dot( line.pos.negate() ) / dir.dot( line.dir );
this._pick = line.pos.add( line.dir.multiply( u ) );
// x3dom.debug.logInfo("No hit at position " + this._pick);
}
}
this._pickingInfo.firstObj = null;
if ( this._currentInputType == x3dom.InputTypes.NAVIGATION &&
( this._pickingInfo.pickObj || this._pickingInfo.shadowObjectId >= 0 ) &&
navType === "lookat" && this._pressX === x && this._pressY === y )
{
var step = ( this._lastButton & 2 ) ? -1 : 1;
var dist = this._pickingInfo.pickPos.subtract( this._from ).length() / tDist;
var laMat = new x3dom.fields.SFMatrix4f();
laMat.setValues( this.getViewMatrix() );
laMat = laMat.inverse();
var from = laMat.e3();
var at = from.subtract( laMat.e2() );
var up = laMat.e1();
dir = this._pickingInfo.pickPos.subtract( from );
var len = dir.length();
dir = dir.normalize();
// var newUp = new x3dom.fields.SFVec3f(0, 1, 0);
var newAt = from.addScaled( dir, len );
var s = dir.cross( up ).normalize();
dir = s.cross( up ).normalize();
if ( step < 0 )
{
dist = ( 0.5 + len + dist ) * 2;
}
var newFrom = newAt.addScaled( dir, dist );
laMat = x3dom.fields.SFMatrix4f.lookAt( newFrom, newAt, up );
laMat = laMat.inverse();
dist = newFrom.subtract( from ).length();
var dur = Math.max( 0.5, Math.log( ( 1 + dist ) / navi._vf.speed ) );
this.animateTo( laMat, this._scene.getViewpoint(), dur );
}
this._dx = 0;
this._dy = 0;
this._lastX = x;
this._lastY = y;
this._lastButton = buttonState;
this._isMoving = false;
};
/**
* On Mouse Over
*
* @param x
* @param y
* @param buttonState
*/
x3dom.Viewarea.prototype.onMouseOver = function ( x, y, buttonState )
{
this._dx = 0;
this._dy = 0;
this._lastButton = 0;
this._isMoving = false;
this._lastX = x;
this._lastY = y;
this._deltaT = 0;
};
/**
* On Mouse Out
*
* @param x
* @param y
* @param buttonState
*/
x3dom.Viewarea.prototype.onMouseOut = function ( x, y, buttonState )
{
this._dx = 0;
this._dy = 0;
this._lastButton = 0;
this._isMoving = false;
this._lastX = x;
this._lastY = y;
this._deltaT = 0;
// if the mouse is moved out of the canvas, reset the list of currently affected pointing sensors
// (this behaves similar to a mouse release inside the canvas)
var i;
var affectedPointingSensorsList = this._doc._nodeBag.affectedPointingSensors;
for ( i = 0; i < affectedPointingSensorsList.length; ++i )
{
affectedPointingSensorsList[ i ].pointerReleased();
}
this._doc._nodeBag.affectedPointingSensors = [];
};
/**
* On Double Click
*
* @param x
* @param y
*/
x3dom.Viewarea.prototype.onDoubleClick = function ( x, y )
{
if ( this._doc._x3dElem.hasAttribute( "disableDoubleClick" ) &&
this._doc._x3dElem.getAttribute( "disableDoubleClick" ) === "true" )
{
return;
}
var navi = this._scene.getNavigationInfo();
navi._impl.onDoubleClick( this, x, y );
};
/**
* Handle Mouse Event
*
* @param x
* @param y
* @param buttonState
*/
x3dom.Viewarea.prototype.handleMoveEvt = function ( x, y, buttonState )
{
// pointing sensors might still be in use, if the mouse has previously been pressed over sensor geometry
// (in general, transitions between INTERACTION and NAVIGATION require that the mouse is not pressed)
if ( buttonState == 0 )
{
this._doc._nodeBag.affectedPointingSensors = [];
}
this.prepareEvents( x, y, buttonState, "onmousemove" );
if ( this._pickingInfo.pickObj !== this._pickingInfo.lastObj )
{
if ( this._pickingInfo.lastObj )
{
var obj = this._pickingInfo.pickObj;
this._pickingInfo.pickObj = this._pickingInfo.lastObj;
// call event for lastObj
var affectedPointingSensors_current = this._doc._nodeBag.affectedPointingSensors;
this._doc._nodeBag.affectedPointingSensors = [];
this.prepareEvents( x, y, buttonState, "onmouseout" );
this._doc._nodeBag.affectedPointingSensors = affectedPointingSensors_current;
this._pickingInfo.pickObj = obj;
}
if ( this._pickingInfo.pickObj )
{
// call event for pickObj
this.prepareEvents( x, y, buttonState, "onmouseover" );
}
this._pickingInfo.lastObj = this._pickingInfo.pickObj;
}
};
/**
* On Move
*
* @param x
* @param y
* @param buttonState
*/
x3dom.Viewarea.prototype.onMove = function ( x, y, buttonState )
{
this.handleMoveEvt( x, y, buttonState );
if ( this._lastX < 0 || this._lastY < 0 )
{
this._lastX = x;
this._lastY = y;
}
this._dx = x - this._lastX;
this._dy = y - this._lastY;
this._lastX = x;
this._lastY = y;
};
/**
* On Mouse Move
* multi-touch version of examine mode, called from X3DCanvas.js
*
* @param translation
* @param rotation
*/
x3dom.Viewarea.prototype.onMoveView = function ( translation, rotation )
{
if ( this._currentInputType == x3dom.InputTypes.NAVIGATION )
{
var navi = this._scene.getNavigationInfo();
var viewpoint = this._scene.getViewpoint();
if ( navi.getType() === "examine" )
{
if ( translation )
{
var distance = ( this._scene._lastMax.subtract( this._scene._lastMin ) ).length();
distance = ( ( distance < x3dom.fields.Eps ) ? 1 : distance ) * navi._vf.speed;
translation = translation.multiply( distance );
this._movement = this._movement.add( translation );
this._transMat = viewpoint.getViewMatrix().inverse().mult( x3dom.fields.SFMatrix4f.translation( this._movement ) ).mult( viewpoint.getViewMatrix() );
}
if ( rotation )
{
var center = viewpoint.getCenterOfRotation();
var mat = this.getViewMatrix();
mat.setTranslate( new x3dom.fields.SFVec3f( 0, 0, 0 ) );
this._rotMat = this._rotMat.mult( x3dom.fields.SFMatrix4f.translation( center ) ).mult( mat.inverse() ).mult( rotation ).mult( mat ).mult( x3dom.fields.SFMatrix4f.translation( center.negate() ) );
}
this._isMoving = true;
}
}
};
/**
* On Drag
*
* @param x
* @param y
* @param buttonState
*/
x3dom.Viewarea.prototype.onDrag = function ( x, y, buttonState )
{
// should onmouseover/-out be handled on drag?
this.handleMoveEvt( x, y, buttonState );
if ( this._currentInputType == x3dom.InputTypes.NAVIGATION )
{
this._scene.getNavigationInfo()._impl.onDrag( this, x, y, buttonState );
}
};
/**
* Prepare Events
*
* @param x
* @param y
* @param buttonState
* @param eventType
*/
x3dom.Viewarea.prototype.prepareEvents = function ( x, y, buttonState, eventType )
{
var affectedPointingSensorsList = this._doc._nodeBag.affectedPointingSensors;
var pickMode = this._scene._vf.pickMode.toLowerCase();
var avoidTraversal = ( pickMode.indexOf( "idbuf" ) == 0 ||
pickMode == "color" || pickMode == "texcoord" );
var obj = null;
if ( avoidTraversal )
{
obj = this._pickingInfo.pickObj;
if ( obj )
{
this._pick.setValues( this._pickingInfo.pickPos );
this._pickNorm.setValues( this._pickingInfo.pickNorm );
this.checkEvents( obj, x, y, buttonState, eventType );
if ( eventType === "onclick" )
{ // debug
if ( obj._xmlNode )
{x3dom.debug.logInfo( "Hit \"" + obj._xmlNode.localName + "/ " + obj._DEF + "\"" );}
if ( pickMode === "color" )
{
x3dom.debug.logInfo( "Ray hit color " + this._pick );
}
else if ( pickMode === "idbufid" || pickMode === "texcoord" )
{
x3dom.debug.logInfo( "Ray hit data " + this._pick );
}
else
{ // idbuf idbuf24 box
x3dom.debug.logInfo( "Ray hit at position "
+ this._pick.x.toFixed( 4 ) + " "
+ this._pick.y.toFixed( 4 ) + " "
+ this._pick.z.toFixed( 4 )
);
}
}
}
}
// TODO: this is pretty redundant - but from where should we obtain this event object?
// this also needs to work if there is no picked object, and independent from "avoidTraversal"?
// FIXME: avoidTraversal is only to distinguish between the ancient box and the other render-based pick modes,
// thus it seems the cleanest thing to just remove the old traversal-based and non-functional box mode.
// Concerning background: what about if we unify the onbackgroundClicked event such that there is also
// an onbackgroundMoved event etc?
var event = {
viewarea : this,
target : {}, // should be hit xml element
type : eventType.substr( 2, eventType.length - 2 ),
button : buttonState,
layerX : x,
layerY : y,
worldX : this._pick.x,
worldY : this._pick.y,
worldZ : this._pick.z,
normalX : this._pickNorm.x,
normalY : this._pickNorm.y,
normalZ : this._pickNorm.z,
hitPnt : this._pick.toGL(), // for convenience
hitObject : ( obj && obj._xmlNode ) ? obj._xmlNode : null,
shadowObjectId : this._pickingInfo.shadowObjectId,
cancelBubble : false,
stopPropagation : function () { this.cancelBubble = true; },
preventDefault : function () { this.cancelBubble = true; }
};
// forward event to affected pointing device sensors
this._notifyAffectedPointingSensors( event );
// switch between navigation and interaction
if ( affectedPointingSensorsList.length > 0 )
{
this._currentInputType = x3dom.InputTypes.INTERACTION;
}
else
{
this._currentInputType = x3dom.InputTypes.NAVIGATION;
}
};
/**
* Get Render Mode
*
* @returns {number}
*/
x3dom.Viewarea.prototype.getRenderMode = function ()
{
// this._points == 0 ? TRIANGLES or TRIANGLE_STRIP
// this._points == 1 ? gl.POINTS
// this._points == 2 ? gl.LINES
// TODO: 3 :== surface with additional wireframe render mode
return this._points;
};
/**
* Get Shadowed Lights
*
* @returns {Array}
*/
x3dom.Viewarea.prototype.getShadowedLights = function ()
{
var shadowedLights = [];
var shadowIndex = 0;
var slights = this.getLights();
for ( var i = 0; i < slights.length; i++ )
{
if ( slights[ i ]._vf.shadowIntensity > 0.0 )
{
shadowedLights[ shadowIndex ] = slights[ i ];
shadowIndex++;
}
}
return shadowedLights;
};
/**
* Calculate view frustum split positions for the given number of cascades
*
* @param {Number} numCascades - the number of cascades
* @param {Number} splitFactor - the splitting factor
* @param {Number} splitOffset - the offset for the splits
* @param {Array} postProject - the post projection something
* @param {x3dom.fields.SFMatrix4f} mat_proj - the projection matrix
* @returns {Array} the post projection something
*/
x3dom.Viewarea.prototype.getShadowSplitDepths = function ( numCascades, splitFactor, splitOffset, postProject, mat_proj )
{
var logSplit;
var practSplit = [];
var viewPoint = this._scene.getViewpoint();
var zNear = viewPoint.getNear();
var zFar = viewPoint.getFar();
practSplit[ 0 ] = zNear;
// pseudo near plane for bigger cascades near camera
zNear = zNear + splitOffset * ( zFar - zNear ) / 10;
// calculate split depths according to "practical split scheme"
for ( var i = 1; i < numCascades; i++ )
{
logSplit = zNear * Math.pow( ( zFar / zNear ), i / numCascades );
practSplit[ i ] = splitFactor * logSplit + ( 1 - splitFactor ) * ( zNear + i / ( numCascades * ( zNear - zFar ) ) );
}
practSplit[ numCascades ] = zFar;
// return in view coords
if ( !postProject )
{return practSplit;}
// return in post projective coords
var postProj = [];
for ( var j = 0; j <= numCascades; j++ )
{
postProj[ j ] = mat_proj.multFullMatrixPnt( new x3dom.fields.SFVec3f( 0, 0, -practSplit[ j ] ) ).z;
}
return postProj;
};
/**
* Calculate a matrix to enhance the placement of
* the near and far planes of the light projection matrix
*
* @param WCToLCMatrix
* @returns {x3dom.fields.SFMatrix4f}
*/
x3dom.Viewarea.prototype.getLightCropMatrix = function ( WCToLCMatrix )
{
// get corner points of scene bounds
var sceneMin = x3dom.fields.SFVec3f.copy( this._scene._lastMin );
var sceneMax = x3dom.fields.SFVec3f.copy( this._scene._lastMax );
var sceneCorners = [];
sceneCorners[ 0 ] = new x3dom.fields.SFVec3f( sceneMin.x, sceneMin.y, sceneMin.z );
sceneCorners[ 1 ] = new x3dom.fields.SFVec3f( sceneMin.x, sceneMin.y, sceneMax.z );
sceneCorners[ 2 ] = new x3dom.fields.SFVec3f( sceneMin.x, sceneMax.y, sceneMin.z );
sceneCorners[ 3 ] = new x3dom.fields.SFVec3f( sceneMin.x, sceneMax.y, sceneMax.z );
sceneCorners[ 4 ] = new x3dom.fields.SFVec3f( sceneMax.x, sceneMin.y, sceneMin.z );
sceneCorners[ 5 ] = new x3dom.fields.SFVec3f( sceneMax.x, sceneMin.y, sceneMax.z );
sceneCorners[ 6 ] = new x3dom.fields.SFVec3f( sceneMax.x, sceneMax.y, sceneMin.z );
sceneCorners[ 7 ] = new x3dom.fields.SFVec3f( sceneMax.x, sceneMax.y, sceneMax.z );
// transform scene bounds into light space
var i;
for ( i = 0; i < 8; i++ )
{
sceneCorners[ i ] = WCToLCMatrix.multFullMatrixPnt( sceneCorners[ i ] );
}
// determine min and max values in light space
var minScene = x3dom.fields.SFVec3f.copy( sceneCorners[ 0 ] );
var maxScene = x3dom.fields.SFVec3f.copy( sceneCorners[ 0 ] );
for ( i = 1; i < 8; i++ )
{
minScene.z = Math.min( sceneCorners[ i ].z, minScene.z );
maxScene.z = Math.max( sceneCorners[ i ].z, maxScene.z );
}
var scaleZ = 2.0 / ( maxScene.z - minScene.z );
var offsetZ = -( scaleZ * ( maxScene.z + minScene.z ) ) / 2.0;
// var scaleZ = 1.0 / (maxScene.z - minScene.z);
// var offsetZ = -minScene.z * scaleZ;
var cropMatrix = x3dom.fields.SFMatrix4f.identity();
cropMatrix._22 = scaleZ;
cropMatrix._23 = offsetZ;
return cropMatrix;
};
/**
* Calculate a matrix to fit the given wctolc-matrix to the split boundaries
*
* @param WCToLCMatrix
* @param zNear
* @param zFar
* @param mat_proj
* @returns {x3dom.fields.SFMatrix4f}
*/
x3dom.Viewarea.prototype.getLightFittingMatrix = function ( WCToLCMatrix, zNear, zFar, mat_proj )
{
var mat_view = this.getViewMatrix();
var mat_view_proj = mat_proj.mult( mat_view );
var mat_view_proj_inverse = mat_view_proj.inverse();
// define view frustum corner points in post perspective view space
var frustumCorners = [];
frustumCorners[ 0 ] = new x3dom.fields.SFVec3f( -1, -1, zFar );
frustumCorners[ 1 ] = new x3dom.fields.SFVec3f( -1, -1, zNear );
frustumCorners[ 2 ] = new x3dom.fields.SFVec3f( -1, 1, zFar );
frustumCorners[ 3 ] = new x3dom.fields.SFVec3f( -1, 1, zNear );
frustumCorners[ 4 ] = new x3dom.fields.SFVec3f( 1, -1, zFar );
frustumCorners[ 5 ] = new x3dom.fields.SFVec3f( 1, -1, zNear );
frustumCorners[ 6 ] = new x3dom.fields.SFVec3f( 1, 1, zFar );
frustumCorners[ 7 ] = new x3dom.fields.SFVec3f( 1, 1, zNear );
// transform corner points into post perspective light space
var i;
for ( i = 0; i < 8; i++ )
{
frustumCorners[ i ] = mat_view_proj_inverse.multFullMatrixPnt( frustumCorners[ i ] );
frustumCorners[ i ] = WCToLCMatrix.multFullMatrixPnt( frustumCorners[ i ] );
}
// calculate minimum and maximum values
var minFrustum = x3dom.fields.SFVec3f.copy( frustumCorners[ 0 ] );
var maxFrustum = x3dom.fields.SFVec3f.copy( frustumCorners[ 0 ] );
for ( i = 1; i < 8; i++ )
{
minFrustum.x = Math.min( frustumCorners[ i ].x, minFrustum.x );
minFrustum.y = Math.min( frustumCorners[ i ].y, minFrustum.y );
minFrustum.z = Math.min( frustumCorners[ i ].z, minFrustum.z );
maxFrustum.x = Math.max( frustumCorners[ i ].x, maxFrustum.x );
maxFrustum.y = Math.max( frustumCorners[ i ].y, maxFrustum.y );
maxFrustum.z = Math.max( frustumCorners[ i ].z, maxFrustum.z );
}
// clip values to box (-1,-1,-1),(1,1,1)
function clip ( min, max )
{
var xMin = min.x;
var yMin = min.y;
var zMin = min.z;
var xMax = max.x;
var yMax = max.y;
var zMax = max.z;
if ( xMin > 1.0 || xMax < -1.0 )
{
xMin = -1.0;
xMax = 1.0;
}
else
{
xMin = Math.max( xMin, -1.0 );
xMax = Math.min( xMax, 1.0 );
}
if ( yMin > 1.0 || yMax < -1.0 )
{
yMin = -1.0;
yMax = 1.0;
}
else
{
yMin = Math.max( yMin, -1.0 );
yMax = Math.min( yMax, 1.0 );
}
if ( zMin > 1.0 || zMax < -1.0 )
{
zMin = -1.0;
zMax = 1.0;
}
else
{
zMin = Math.max( zMin, -1.0 );
zMax = Math.min( zMax, 1.0 );
}
var minValues = new x3dom.fields.SFVec3f( xMin, yMin, zMin );
var maxValues = new x3dom.fields.SFVec3f( xMax, yMax, zMax );
return new x3dom.fields.BoxVolume( minValues, maxValues );
}
var frustumBB = clip( minFrustum, maxFrustum );
// define fitting matrix
var scaleX = 2.0 / ( frustumBB.max.x - frustumBB.min.x );
var scaleY = 2.0 / ( frustumBB.max.y - frustumBB.min.y );
var offsetX = -( scaleX * ( frustumBB.max.x + frustumBB.min.x ) ) / 2.0;
var offsetY = -( scaleY * ( frustumBB.max.y + frustumBB.min.y ) ) / 2.0;
var fittingMatrix = x3dom.fields.SFMatrix4f.identity();
fittingMatrix._00 = scaleX;
fittingMatrix._11 = scaleY;
fittingMatrix._03 = offsetX;
fittingMatrix._13 = offsetY;
return fittingMatrix;
};
/**
* X3DOM JavaScript Library
* http://www.x3dom.org
*
* (C)2009 Fraunhofer IGD, Darmstadt, Germany
* Dual licensed under the MIT and GPL
*
* Based on code originally provided by
* Philip Taylor: http://philip.html5.org
*/
/**
* The x3dom.fields namespace.
* @namespace x3dom.fields
*/
x3dom.fields = {};
// shortcut for convenience and speedup
var VecMath = x3dom.fields;
// Epsilon
x3dom.fields.Eps = 0.000001;
///////////////////////////////////////////////////////////////////////////////
// Single-Field Definitions
///////////////////////////////////////////////////////////////////////////////
/**
* Constructor. You must either specify all argument values or no
* argument. In the latter case, an identity matrix will be created.
*
* @class Represents a 4x4 matrix in row major format.
* @param {Number} [_00=1] - value at [0,0]
* @param {Number} [_01=0] - value at [0,1]
* @param {Number} [_02=0] - value at [0,2]
* @param {Number} [_03=0] - value at [0,3]
* @param {Number} [_10=0] - value at [1,0]
* @param {Number} [_11=1] - value at [1,1]
* @param {Number} [_12=0] - value at [1,2]
* @param {Number} [_13=0] - value at [1,3]
* @param {Number} [_20=0] - value at [2,0]
* @param {Number} [_21=0] - value at [2,1]
* @param {Number} [_22=1] - value at [2,2]
* @param {Number} [_23=0] - value at [2,3]
* @param {Number} [_30=0] - value at [3,0]
* @param {Number} [_31=0] - value at [3,1]
* @param {Number} [_32=0] - value at [3,2]
* @param {Number} [_33=1] - value at [3,3]
*/
x3dom.fields.SFMatrix4f = function ( _00, _01, _02, _03,
_10, _11, _12, _13,
_20, _21, _22, _23,
_30, _31, _32, _33 )
{
if ( arguments.length === 0 )
{
this._00 = 1; this._01 = 0; this._02 = 0; this._03 = 0;
this._10 = 0; this._11 = 1; this._12 = 0; this._13 = 0;
this._20 = 0; this._21 = 0; this._22 = 1; this._23 = 0;
this._30 = 0; this._31 = 0; this._32 = 0; this._33 = 1;
}
else
{
this._00 = _00; this._01 = _01; this._02 = _02; this._03 = _03;
this._10 = _10; this._11 = _11; this._12 = _12; this._13 = _13;
this._20 = _20; this._21 = _21; this._22 = _22; this._23 = _23;
this._30 = _30; this._31 = _31; this._32 = _32; this._33 = _33;
}
};
/**
* Returns the first column vector of the matrix.
*
* @returns {x3dom.fields.SFVec3f} the vector
*/
x3dom.fields.SFMatrix4f.prototype.e0 = function ()
{
var baseVec = new x3dom.fields.SFVec3f( this._00, this._10, this._20 );
return baseVec.normalize();
};
/**
* Returns the second column vector of the matrix.
*
* @returns {x3dom.fields.SFVec3f} the vector
*/
x3dom.fields.SFMatrix4f.prototype.e1 = function ()
{
var baseVec = new x3dom.fields.SFVec3f( this._01, this._11, this._21 );
return baseVec.normalize();
};
/**
* Returns the third column vector of the matrix.
*
* @returns {x3dom.fields.SFVec3f} the vector
*/
x3dom.fields.SFMatrix4f.prototype.e2 = function ()
{
var baseVec = new x3dom.fields.SFVec3f( this._02, this._12, this._22 );
return baseVec.normalize();
};
/**
* Returns the fourth column vector of the matrix.
*
* @returns {x3dom.fields.SFVec3f} the vector
*/
x3dom.fields.SFMatrix4f.prototype.e3 = function ()
{
return new x3dom.fields.SFVec3f( this._03, this._13, this._23 );
};
/**
* Returns a copy of the argument matrix.
*
* @param {x3dom.fields.SFMatrix4f} that - the matrix to copy
* @returns {x3dom.fields.SFMatrix4f} the copy
*/
x3dom.fields.SFMatrix4f.copy = function ( that )
{
return new x3dom.fields.SFMatrix4f(
that._00, that._01, that._02, that._03,
that._10, that._11, that._12, that._13,
that._20, that._21, that._22, that._23,
that._30, that._31, that._32, that._33
);
};
/**
* Returns a copy of the matrix.
*
* @returns {x3dom.fields.SFMatrix4f} the copy
*/
x3dom.fields.SFMatrix4f.prototype.copy = function ()
{
return x3dom.fields.SFMatrix4f.copy( this );
};
/**
* Returns a SFMatrix4f identity matrix.
*
* @returns {x3dom.fields.SFMatrix4f} the new identity matrix
*/
x3dom.fields.SFMatrix4f.identity = function ()
{
return new x3dom.fields.SFMatrix4f(
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
);
};
/**
* Returns a new null matrix.
*
* @returns {x3dom.fields.SFMatrix4f} the new null matrix
*/
x3dom.fields.SFMatrix4f.zeroMatrix = function ()
{
return new x3dom.fields.SFMatrix4f(
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0
);
};
/**
* Returns a new translation matrix.
*
* @param {x3dom.fields.SFVec3f} vec - vector that describes the desired
* translation
* @returns {x3dom.fields.SFMatrix4f} the new identity matrix
*/
x3dom.fields.SFMatrix4f.translation = function ( vec )
{
return new x3dom.fields.SFMatrix4f(
1, 0, 0, vec.x,
0, 1, 0, vec.y,
0, 0, 1, vec.z,
0, 0, 0, 1
);
};
/**
* Returns a new rotation matrix, rotating around the x axis.
*
* @param {Number} a - angle in radians
* @returns {x3dom.fields.SFMatrix4f} the new rotation matrix
*/
x3dom.fields.SFMatrix4f.rotationX = function ( a )
{
var c = Math.cos( a );
var s = Math.sin( a );
return new x3dom.fields.SFMatrix4f(
1, 0, 0, 0,
0, c, -s, 0,
0, s, c, 0,
0, 0, 0, 1
);
};
/**
* Returns a new rotation matrix, rotating around the y axis.
*
* @param {Number} a - angle in radians
* @returns {x3dom.fields.SFMatrix4f} the new rotation matrix
*/
x3dom.fields.SFMatrix4f.rotationY = function ( a )
{
var c = Math.cos( a );
var s = Math.sin( a );
return new x3dom.fields.SFMatrix4f(
c, 0, s, 0,
0, 1, 0, 0,
-s, 0, c, 0,
0, 0, 0, 1
);
};
/**
* Returns a new rotation matrix, rotating around the z axis.
*
* @param {Number} a - angle in radians
* @returns {x3dom.fields.SFMatrix4f} the new rotation matrix
*/
x3dom.fields.SFMatrix4f.rotationZ = function ( a )
{
var c = Math.cos( a );
var s = Math.sin( a );
return new x3dom.fields.SFMatrix4f(
c, -s, 0, 0,
s, c, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
);
};
/**
* Returns a new scale matrix.
*
* @param {x3dom.fields.SFVec3f} vec - vector containing scale factors
* along the three main axes
* @returns {x3dom.fields.SFMatrix4f} the new scale matrix
*/
x3dom.fields.SFMatrix4f.scale = function ( vec )
{
return new x3dom.fields.SFMatrix4f(
vec.x, 0, 0, 0,
0, vec.y, 0, 0,
0, 0, vec.z, 0,
0, 0, 0, 1
);
};
/**
* Returns a new camera matrix, using the given "look at" parameters.
*
* @param {x3dom.fields.SFVec3f} from - eye point
* @param {x3dom.fields.SFVec3f} at - focus ("look at") point
* @param {x3dom.fields.SFVec3f} up - up vector
* @returns {x3dom.fields.SFMatrix4f} the new camera matrix
*/
x3dom.fields.SFMatrix4f.lookAt = function ( from, at, up )
{
var view = from.subtract( at ).normalize();
var right = up.normalize().cross( view ).normalize();
// check if zero vector, i.e. linearly dependent
if ( right.dot( right ) < x3dom.fields.Eps )
{
x3dom.debug.logWarning( "View matrix is linearly dependent." );
return x3dom.fields.SFMatrix4f.translation( from );
}
var newUp = view.cross( right ).normalize();
var tmp = x3dom.fields.SFMatrix4f.identity();
tmp.setValue( right, newUp, view, from );
return tmp;
};
/**
* Returns a new perspective projection frustum.
*
* @param {Number} left - Left
* @param {Number} right - Right
* @param {Number} bottom - Bottom
* @param {Number} top - Top
* @param {Number} near - near clipping distance
* @param {Number} far - far clipping distance
*/
x3dom.fields.SFMatrix4f.perspectiveFrustum = function ( left, right, bottom, top, near, far )
{
return new x3dom.fields.SFMatrix4f(
2 * near / ( right - left ), 0, ( right + left ) / ( right - left ), 0,
0, 2 * near / ( top - bottom ), ( top + bottom ) / ( top - bottom ), 0,
0, 0, -( far + near ) / ( far - near ), -2 * far * near / ( far - near ),
0, 0, -1, 0
);
};
/**
* Returns a new perspective projection matrix.
*
* @param {Number} fov - field-of-view angle in radians
* @param {Number} aspect - aspect ratio (width / height)
* @param {Number} near - near clipping distance
* @param {Number} far - far clipping distance
* @returns {x3dom.fields.SFMatrix4f} the new projection matrix
*/
x3dom.fields.SFMatrix4f.perspective = function ( fov, aspect, near, far )
{
var f = 1 / Math.tan( fov / 2 );
return new x3dom.fields.SFMatrix4f(
f / aspect, 0, 0, 0,
0, f, 0, 0,
0, 0, ( near + far ) / ( near - far ), 2 * near * far / ( near - far ),
0, 0, -1, 0
);
};
/**
* Returns a new orthogonal projection matrix.
*
* @param {Number} left - left border value of the view area
* @param {Number} right - right border value of the view area
* @param {Number} bottom - bottom border value of the view area
* @param {Number} top - top border value of the view area
* @param {Number} near - near clipping distance
* @param {Number} far - far clipping distance
* @param {Number} [aspect=1.0] - desired aspect ratio (width / height)
* of the projected image
* @returns {x3dom.fields.SFMatrix4f} the new projection matrix
*/
x3dom.fields.SFMatrix4f.ortho = function ( left, right, bottom, top,
near, far, aspect )
{
var rl = ( right - left ) / 2; // hs
var tb = ( top - bottom ) / 2; // vs
var fn = far - near;
if ( aspect === undefined )
{aspect = 1.0;}
if ( aspect < ( rl / tb ) )
{
tb = rl / aspect;
}
else
{
rl = tb * aspect;
}
left = -rl;
right = rl;
bottom = -tb;
top = tb;
rl *= 2;
tb *= 2;
return new x3dom.fields.SFMatrix4f(
2 / rl, 0, 0, -( right + left ) / rl,
0, 2 / tb, 0, -( top + bottom ) / tb,
0, 0, -2 / fn, -( far + near ) / fn,
0, 0, 0, 1
);
};
/**
* Sets the translation components of a homogenous transform matrix.
*
* @param {x3dom.fields.SFVec3f} vec - the translation vector
*/
x3dom.fields.SFMatrix4f.prototype.setTranslate = function ( vec )
{
this._03 = vec.x;
this._13 = vec.y;
this._23 = vec.z;
};
/**
* Sets the scale components of a homogenous transform matrix.
*
* @param {x3dom.fields.SFVec3f} vec - vector containing scale factors
* along the three main axes
*/
x3dom.fields.SFMatrix4f.prototype.setScale = function ( vec )
{
this._00 = vec.x;
this._11 = vec.y;
this._22 = vec.z;
};
/**
* Sets the rotation components of a homogenous transform matrix.
*
* @param {x3dom.fields.Quaternion} quat - quaternion that describes
* the rotation
*/
x3dom.fields.SFMatrix4f.prototype.setRotate = function ( quat )
{
var xx = quat.x * quat.x;
var xy = quat.x * quat.y;
var xz = quat.x * quat.z;
var yy = quat.y * quat.y;
var yz = quat.y * quat.z;
var zz = quat.z * quat.z;
var wx = quat.w * quat.x;
var wy = quat.w * quat.y;
var wz = quat.w * quat.z;
this._00 = 1 - 2 * ( yy + zz );
this._01 = 2 * ( xy - wz );
this._02 = 2 * ( xz + wy );
this._10 = 2 * ( xy + wz );
this._11 = 1 - 2 * ( xx + zz );
this._12 = 2 * ( yz - wx );
this._20 = 2 * ( xz - wy );
this._21 = 2 * ( yz + wx );
this._22 = 1 - 2 * ( xx + yy );
};
/**
* Creates a new matrix from a column major string representation, with
* values separated by commas or whitespace
*
* @param {String} str - string to parse
* @returns {x3dom.fields.SFMatrix4f} the new matrix
*/
x3dom.fields.SFMatrix4f.parseRotation = function ( str )
{
var m = /^([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)$/.exec( str );
var x = +m[ 1 ],
y = +m[ 2 ],
z = +m[ 3 ],
a = +m[ 4 ];
var d = Math.sqrt( x * x + y * y + z * z );
if ( d === 0 )
{
x = 1;
y = z = 0;
}
else
{
x /= d;
y /= d;
z /= d;
}
var c = Math.cos( a );
var s = Math.sin( a );
var t = 1 - c;
return new x3dom.fields.SFMatrix4f(
t * x * x + c, t * x * y + s * z, t * x * z - s * y, 0,
t * x * y - s * z, t * y * y + c, t * y * z + s * x, 0,
t * x * z + s * y, t * y * z - s * x, t * z * z + c, 0,
0, 0, 0, 1
).transpose();
};
/**
* Creates a new matrix from a X3D-conformant string representation
*
* @param {String} str - string to parse
* @returns {x3dom.fields.SFMatrix4f} the new rotation matrix
*/
x3dom.fields.SFMatrix4f.parse = function ( str )
{
var needTranspose = false;
var val = /matrix.*\((.+)\)/;
if ( val.exec( str ) )
{
str = RegExp.$1;
needTranspose = true;
}
var arr = str.split( /[,\s]+/ ).map( function ( n ) { return +n; } );
if ( arr.length >= 16 )
{
if ( !needTranspose )
{
return new x3dom.fields.SFMatrix4f(
arr[ 0 ], arr[ 1 ], arr[ 2 ], arr[ 3 ],
arr[ 4 ], arr[ 5 ], arr[ 6 ], arr[ 7 ],
arr[ 8 ], arr[ 9 ], arr[ 10 ], arr[ 11 ],
arr[ 12 ], arr[ 13 ], arr[ 14 ], arr[ 15 ]
);
}
else
{
return new x3dom.fields.SFMatrix4f(
arr[ 0 ], arr[ 4 ], arr[ 8 ], arr[ 12 ],
arr[ 1 ], arr[ 5 ], arr[ 9 ], arr[ 13 ],
arr[ 2 ], arr[ 6 ], arr[ 10 ], arr[ 14 ],
arr[ 3 ], arr[ 7 ], arr[ 11 ], arr[ 15 ]
);
}
}
else if ( arr.length === 6 )
{
return new x3dom.fields.SFMatrix4f(
arr[ 0 ], arr[ 1 ], 0, arr[ 4 ],
arr[ 2 ], arr[ 3 ], 0, arr[ 5 ],
0, 0, 1, 0,
0, 0, 0, 1
);
}
else
{
x3dom.debug.logWarning( "SFMatrix4f - can't parse string: " + str );
return x3dom.fields.SFMatrix4f.identity();
}
};
/**
* Returns the result of multiplying this matrix with the given one,
* using "post-multiplication" / "right multiply".
*
* @param {x3dom.fields.SFMatrix4f} that - matrix to multiply with this
* one
* @returns {x3dom.fields.SFMatrix4f} resulting matrix
*/
x3dom.fields.SFMatrix4f.prototype.mult = function ( that )
{
return new x3dom.fields.SFMatrix4f(
this._00 * that._00 + this._01 * that._10 + this._02 * that._20 + this._03 * that._30,
this._00 * that._01 + this._01 * that._11 + this._02 * that._21 + this._03 * that._31,
this._00 * that._02 + this._01 * that._12 + this._02 * that._22 + this._03 * that._32,
this._00 * that._03 + this._01 * that._13 + this._02 * that._23 + this._03 * that._33,
this._10 * that._00 + this._11 * that._10 + this._12 * that._20 + this._13 * that._30,
this._10 * that._01 + this._11 * that._11 + this._12 * that._21 + this._13 * that._31,
this._10 * that._02 + this._11 * that._12 + this._12 * that._22 + this._13 * that._32,
this._10 * that._03 + this._11 * that._13 + this._12 * that._23 + this._13 * that._33,
this._20 * that._00 + this._21 * that._10 + this._22 * that._20 + this._23 * that._30,
this._20 * that._01 + this._21 * that._11 + this._22 * that._21 + this._23 * that._31,
this._20 * that._02 + this._21 * that._12 + this._22 * that._22 + this._23 * that._32,
this._20 * that._03 + this._21 * that._13 + this._22 * that._23 + this._23 * that._33,
this._30 * that._00 + this._31 * that._10 + this._32 * that._20 + this._33 * that._30,
this._30 * that._01 + this._31 * that._11 + this._32 * that._21 + this._33 * that._31,
this._30 * that._02 + this._31 * that._12 + this._32 * that._22 + this._33 * that._32,
this._30 * that._03 + this._31 * that._13 + this._32 * that._23 + this._33 * that._33
);
};
/**
* Transforms a given 3D point, using this matrix as a homogenous
* transform matrix (ignores projection part of matrix for speedup in
* standard cases).
*
* @param {x3dom.fields.SFVec3f} vec - point to transform
* @returns {x3dom.fields.SFVec3f} resulting point
*/
x3dom.fields.SFMatrix4f.prototype.multMatrixPnt = function ( vec )
{
return new x3dom.fields.SFVec3f(
this._00 * vec.x + this._01 * vec.y + this._02 * vec.z + this._03,
this._10 * vec.x + this._11 * vec.y + this._12 * vec.z + this._13,
this._20 * vec.x + this._21 * vec.y + this._22 * vec.z + this._23
);
};
/**
* Transforms a given 3D vector, using this matrix as a homogenous
* transform matrix.
*
* @param {x3dom.fields.SFVec3f} vec - vector to transform
* @returns {x3dom.fields.SFVec3f} resulting vector
*/
x3dom.fields.SFMatrix4f.prototype.multMatrixVec = function ( vec )
{
return new x3dom.fields.SFVec3f(
this._00 * vec.x + this._01 * vec.y + this._02 * vec.z,
this._10 * vec.x + this._11 * vec.y + this._12 * vec.z,
this._20 * vec.x + this._21 * vec.y + this._22 * vec.z
);
};
/**
* Transforms a given 3D point, using this matrix as a transform matrix
* (also includes projection part of matrix - required for e.g.
* modelview-projection matrix). The resulting point is normalized by a
* w component.
*
* @param {x3dom.fields.SFVec3f} vec - point to transform
* @returns {x3dom.fields.SFVec3f} resulting point
*/
x3dom.fields.SFMatrix4f.prototype.multFullMatrixPnt = function ( vec )
{
var w = this._30 * vec.x + this._31 * vec.y + this._32 * vec.z + this._33;
if ( w )
{
w = 1.0 / w;
}
return new x3dom.fields.SFVec3f(
( this._00 * vec.x + this._01 * vec.y + this._02 * vec.z + this._03 ) * w,
( this._10 * vec.x + this._11 * vec.y + this._12 * vec.z + this._13 ) * w,
( this._20 * vec.x + this._21 * vec.y + this._22 * vec.z + this._23 ) * w
);
};
/**
* Transforms a given 3D point, using this matrix as a transform matrix
* (also includes projection part of matrix - required for e.g.
* modelview-projection matrix). The resulting point is normalized by a
* w component.
*
* @param {x3dom.fields.SFVec4f} plane - plane to transform
* @returns {x3dom.fields.SFVec4f} resulting plane
*/
x3dom.fields.SFMatrix4f.prototype.multMatrixPlane = function ( plane )
{
var normal = new x3dom.fields.SFVec3f( plane.x, plane.y, plane.z );
var memberPnt = normal.multiply( -plane.w );
memberPnt = this.multMatrixPnt( memberPnt );
var invTranspose = this.inverse().transpose();
normal = invTranspose.multMatrixVec( normal );
var d = -normal.dot( memberPnt );
return new x3dom.fields.SFVec4f( normal.x, normal.y, normal.z, d );
};
/**
* Returns a transposed version of this matrix.
*
* @returns {x3dom.fields.SFMatrix4f} resulting matrix
*/
x3dom.fields.SFMatrix4f.prototype.transpose = function ()
{
return new x3dom.fields.SFMatrix4f(
this._00, this._10, this._20, this._30,
this._01, this._11, this._21, this._31,
this._02, this._12, this._22, this._32,
this._03, this._13, this._23, this._33
);
};
/**
* Returns a negated version of this matrix.
*
* @returns {x3dom.fields.SFMatrix4f} resulting matrix
*/
x3dom.fields.SFMatrix4f.prototype.negate = function ()
{
return new x3dom.fields.SFMatrix4f(
-this._00, -this._01, -this._02, -this._03,
-this._10, -this._11, -this._12, -this._13,
-this._20, -this._21, -this._22, -this._23,
-this._30, -this._31, -this._32, -this._33
);
};
/**
* Returns a scaled version of this matrix.
*
* @param {Number} s - scale factor
* @returns {x3dom.fields.SFMatrix4f} resulting matrix
*/
x3dom.fields.SFMatrix4f.prototype.multiply = function ( s )
{
return new x3dom.fields.SFMatrix4f(
s * this._00, s * this._01, s * this._02, s * this._03,
s * this._10, s * this._11, s * this._12, s * this._13,
s * this._20, s * this._21, s * this._22, s * this._23,
s * this._30, s * this._31, s * this._32, s * this._33
);
};
/**
* Returns the result of adding the given matrix to this matrix.
*
* @param {x3dom.fields.SFMatrix4f} that - the other matrix
* @returns {x3dom.fields.SFMatrix4f} resulting matrix
*/
x3dom.fields.SFMatrix4f.prototype.add = function ( that )
{
return new x3dom.fields.SFMatrix4f(
this._00 + that._00, this._01 + that._01, this._02 + that._02, this._03 + that._03,
this._10 + that._10, this._11 + that._11, this._12 + that._12, this._13 + that._13,
this._20 + that._20, this._21 + that._21, this._22 + that._22, this._23 + that._23,
this._30 + that._30, this._31 + that._31, this._32 + that._32, this._33 + that._33
);
};
/**
* Returns the result of adding the given matrix to this matrix
* using an additional scale factor for the argument matrix.
*
* @param {x3dom.fields.SFMatrix4f} that - the other matrix
* @param {Number} s - the scale factor
* @returns {x3dom.fields.SFMatrix4f} resulting matrix
*/
x3dom.fields.SFMatrix4f.prototype.addScaled = function ( that, s )
{
return new x3dom.fields.SFMatrix4f(
this._00 + s * that._00, this._01 + s * that._01, this._02 + s * that._02, this._03 + s * that._03,
this._10 + s * that._10, this._11 + s * that._11, this._12 + s * that._12, this._13 + s * that._13,
this._20 + s * that._20, this._21 + s * that._21, this._22 + s * that._22, this._23 + s * that._23,
this._30 + s * that._30, this._31 + s * that._31, this._32 + s * that._32, this._33 + s * that._33
);
};
/**
* Fills the values of this matrix with the values of the other one.
*
* @param {x3dom.fields.SFMatrix4f} that - the other matrix
*/
x3dom.fields.SFMatrix4f.prototype.setValues = function ( that )
{
this._00 = that._00;
this._01 = that._01;
this._02 = that._02;
this._03 = that._03;
this._10 = that._10;
this._11 = that._11;
this._12 = that._12;
this._13 = that._13;
this._20 = that._20;
this._21 = that._21;
this._22 = that._22;
this._23 = that._23;
this._30 = that._30;
this._31 = that._31;
this._32 = that._32;
this._33 = that._33;
};
/**
* Fills the upper left 3x3 or 3x4 values of this matrix, using the
* given (three or four) column vectors.
*
* @param {x3dom.fields.SFVec3f} v1 - first column vector
* @param {x3dom.fields.SFVec3f} v2 - second column vector
* @param {x3dom.fields.SFVec3f} v3 - third column vector
* @param {x3dom.fields.SFVec3f} [v4=undefined] - fourth column vector
*/
x3dom.fields.SFMatrix4f.prototype.setValue = function ( v1, v2, v3, v4 )
{
this._00 = v1.x;
this._01 = v2.x;
this._02 = v3.x;
this._10 = v1.y;
this._11 = v2.y;
this._12 = v3.y;
this._20 = v1.z;
this._21 = v2.z;
this._22 = v3.z;
this._30 = 0;
this._31 = 0;
this._32 = 0;
if ( arguments.length > 3 )
{
this._03 = v4.x;
this._13 = v4.y;
this._23 = v4.z;
this._33 = 1;
}
};
/**
* Fills the values of this matrix, using the given array.
*
* @param {Number[]} a - array, the first 16 values will be used to
* initialize the matrix
*/
x3dom.fields.SFMatrix4f.prototype.setFromArray = function ( a )
{
this._00 = a[ 0 ];
this._01 = a[ 4 ];
this._02 = a[ 8 ];
this._03 = a[ 12 ];
this._10 = a[ 1 ];
this._11 = a[ 5 ];
this._12 = a[ 9 ];
this._13 = a[ 13 ];
this._20 = a[ 2 ];
this._21 = a[ 6 ];
this._22 = a[ 10 ];
this._23 = a[ 14 ];
this._30 = a[ 3 ];
this._31 = a[ 7 ];
this._32 = a[ 11 ];
this._33 = a[ 15 ];
return this;
};
/**
* Fills the values of this matrix, using the given array.
* @param {Number[]} a - array, the first 16 values will be used to
* initialize the matrix
*/
x3dom.fields.SFMatrix4f.fromArray = function ( a )
{
var m = new x3dom.fields.SFMatrix4f();
m._00 = a[ 0 ]; m._01 = a[ 4 ]; m._02 = a[ 8 ]; m._03 = a[ 12 ];
m._10 = a[ 1 ]; m._11 = a[ 5 ]; m._12 = a[ 9 ]; m._13 = a[ 13 ];
m._20 = a[ 2 ]; m._21 = a[ 6 ]; m._22 = a[ 10 ]; m._23 = a[ 14 ];
m._30 = a[ 3 ]; m._31 = a[ 7 ]; m._32 = a[ 11 ]; m._33 = a[ 15 ];
return m;
};
/**
* Sets the rotation, translation, and scaling of this matrix using
* the respective transformation components, and returns this matrix.
*
* @param {x3dom.fields.Quaternion} rotation - the rotation part
* @param {x3dom.fields.SFVec3f} translation - the 3D translation
* vector
* @param {x3dom.fields.SFVec3f} scale - the non-uniform scaling
* factors
* @returns {x3dom.fields.SFMatrix4f} the modified matrix
*/
x3dom.fields.SFMatrix4f.prototype.fromRotationTranslationScale = function ( rotation, translation, scale )
{
translation = translation || new x3dom.fields.SFVec3f(); //[ 0.2, -0.3, -0.3 ];
rotation = rotation || new x3dom.fields.Quaternion();
scale = scale || new x3dom.fields.SFVec3f( 1, 1, 1 );
var x = rotation.x,
y = rotation.y,
z = rotation.z,
w = rotation.w;
var x2 = x + x;
var y2 = y + y;
var z2 = z + z;
var xx = x * x2;
var xy = x * y2;
var xz = x * z2;
var yy = y * y2;
var yz = y * z2;
var zz = z * z2;
var wx = w * x2;
var wy = w * y2;
var wz = w * z2;
this._00 = ( 1 - ( yy + zz ) ) * scale.x;
this._10 = ( xy + wz ) * scale.x;
this._20 = ( xz - wy ) * scale.x;
this._30 = 0;
this._01 = ( xy - wz ) * scale.y;
this._11 = ( 1 - ( xx + zz ) ) * scale.y;
this._21 = ( yz + wx ) * scale.y;
this._31 = 0;
this._02 = ( xz + wy ) * scale.z;
this._12 = ( yz - wx ) * scale.z;
this._22 = ( 1 - ( xx + yy ) ) * scale.z;
this._32 = 0;
this._03 = translation.x;
this._13 = translation.y;
this._23 = translation.z;
this._33 = 1;
return this;
};
/**
* Creates and returns a new 4x4 transformation matrix defined by the
* rotation, translation, and scaling components.
*
* @param {x3dom.fields.Quaternion} rotation - the rotation part
* @param {x3dom.fields.SFVec3f} translation - the 3D translation vector
* @param {x3dom.fields.SFVec3f} scale - the non-uniform scaling
* factors
* @returns {x3dom.fields.SFMatrix4f} the created transformation matrix
*/
x3dom.fields.SFMatrix4f.fromRotationTranslationScale = function ( rotation, translation, scale )
{
translation = translation || new x3dom.fields.SFVec3f(); //[ 0.2, -0.3, -0.3 ];
rotation = rotation || new x3dom.fields.Quaternion();
scale = scale || new x3dom.fields.SFVec3f( 1, 1, 1 );
var m = new x3dom.fields.SFMatrix4f();
var x = rotation.x,
y = rotation.y,
z = rotation.z,
w = rotation.w;
var x2 = x + x;
var y2 = y + y;
var z2 = z + z;
var xx = x * x2;
var xy = x * y2;
var xz = x * z2;
var yy = y * y2;
var yz = y * z2;
var zz = z * z2;
var wx = w * x2;
var wy = w * y2;
var wz = w * z2;
m._00 = ( 1 - ( yy + zz ) ) * scale.x;
m._10 = ( xy + wz ) * scale.x;
m._20 = ( xz - wy ) * scale.x;
m._30 = 0;
m._01 = ( xy - wz ) * scale.y;
m._11 = ( 1 - ( xx + zz ) ) * scale.y;
m._21 = ( yz + wx ) * scale.y;
m._31 = 0;
m._02 = ( xz + wy ) * scale.z;
m._12 = ( yz - wx ) * scale.z;
m._22 = ( 1 - ( xx + yy ) ) * scale.z;
m._32 = 0;
m._03 = translation.x;
m._13 = translation.y;
m._23 = translation.z;
m._33 = 1;
return m;
};
/**
* Returns a column major version of this matrix, packed into a single
* array.
*
* @returns {Number[]} resulting array of 16 values
*/
x3dom.fields.SFMatrix4f.prototype.toGL = function ()
{
return [
this._00, this._10, this._20, this._30,
this._01, this._11, this._21, this._31,
this._02, this._12, this._22, this._32,
this._03, this._13, this._23, this._33
];
};
/**
* Creates and returns a new ``SFMatrix4f'' with the elements,
* construed as being in column-major format, copied from the supplied
* array.
*
* @param {Number[]} array - an array of at least 16 numbers,
* the first 16 of which will be construed
* as the new matrix data in column-major
* format
* @returns {SFMatrix4f} a new matrix based upon the ``array''
* data transferred from its imputed column-major
* format into the row-major format
*/
x3dom.fields.SFMatrix4f.fromGL = function ( array )
{
var newMatrix = new x3dom.fields.SFMatrix4f();
newMatrix._00 = array[ 0 ];
newMatrix._01 = array[ 4 ];
newMatrix._02 = array[ 8 ];
newMatrix._03 = array[ 12 ];
newMatrix._10 = array[ 1 ];
newMatrix._11 = array[ 5 ];
newMatrix._12 = array[ 9 ];
newMatrix._13 = array[ 13 ];
newMatrix._20 = array[ 2 ];
newMatrix._21 = array[ 6 ];
newMatrix._22 = array[ 10 ];
newMatrix._23 = array[ 14 ];
newMatrix._30 = array[ 3 ];
newMatrix._31 = array[ 7 ];
newMatrix._32 = array[ 11 ];
newMatrix._33 = array[ 15 ];
return newMatrix;
};
/**
* Returns the value of this matrix at a given position.
*
* @param {Number} i - row index (starting with 0)
* @param {Number} j - column index (starting with 0)
* @returns {Number} the entry at the specified position
*/
x3dom.fields.SFMatrix4f.prototype.at = function ( i, j )
{
var field = "_" + i + j;
return this[ field ];
};
/**
* Sets the value of this matrix at a given position.
*
* @param {Number} i - row index (starting with 0)
* @param {Number} j - column index (starting with 0)
* @param {Number} newEntry - the new value to store at position (i, j)
* @returns {x3dom.fields.SFMatrix4f} this modified matrix
*/
x3dom.fields.SFMatrix4f.prototype.setAt = function ( i, j, newEntry )
{
var field = "_" + i + j;
this[ field ] = newEntry;
return this;
};
/**
* Computes the square root of the matrix, assuming that its determinant
* is greater than zero.
*
* @returns {SFMatrix4f} a matrix containing the result
*/
x3dom.fields.SFMatrix4f.prototype.sqrt = function ()
{
var Y = x3dom.fields.SFMatrix4f.identity();
var result = x3dom.fields.SFMatrix4f.copy( this );
for ( var i = 0; i < 6; i++ )
{
var iX = result.inverse();
var iY = ( i == 0 ) ? x3dom.fields.SFMatrix4f.identity() : Y.inverse();
var rd = result.det(),
yd = Y.det();
var g = Math.abs( Math.pow( rd * yd, -0.125 ) );
var ig = 1.0 / g;
result = result.multiply( g );
result = result.addScaled( iY, ig );
result = result.multiply( 0.5 );
Y = Y.multiply( g );
Y = Y.addScaled( iX, ig );
Y = Y.multiply( 0.5 );
}
return result;
};
/**
* Returns the largest absolute value of all entries in the matrix.
* This is only a helper for calculating log and not the usual
* Infinity-norm for matrices.
*
* @returns {Number} the largest absolute value
*/
x3dom.fields.SFMatrix4f.prototype.normInfinity = function ()
{
var t = 0,
m = 0;
if ( ( t = Math.abs( this._00 ) ) > m )
{
m = t;
}
if ( ( t = Math.abs( this._01 ) ) > m )
{
m = t;
}
if ( ( t = Math.abs( this._02 ) ) > m )
{
m = t;
}
if ( ( t = Math.abs( this._03 ) ) > m )
{
m = t;
}
if ( ( t = Math.abs( this._10 ) ) > m )
{
m = t;
}
if ( ( t = Math.abs( this._11 ) ) > m )
{
m = t;
}
if ( ( t = Math.abs( this._12 ) ) > m )
{
m = t;
}
if ( ( t = Math.abs( this._13 ) ) > m )
{
m = t;
}
if ( ( t = Math.abs( this._20 ) ) > m )
{
m = t;
}
if ( ( t = Math.abs( this._21 ) ) > m )
{
m = t;
}
if ( ( t = Math.abs( this._22 ) ) > m )
{
m = t;
}
if ( ( t = Math.abs( this._23 ) ) > m )
{
m = t;
}
if ( ( t = Math.abs( this._30 ) ) > m )
{
m = t;
}
if ( ( t = Math.abs( this._31 ) ) > m )
{
m = t;
}
if ( ( t = Math.abs( this._32 ) ) > m )
{
m = t;
}
if ( ( t = Math.abs( this._33 ) ) > m )
{
m = t;
}
return m;
};
/**
* Returns the 1-norm of the upper left 3x3 part of this matrix.
* The 1-norm is also known as maximum absolute column sum norm.
*
* @returns {Number} the resulting number
*/
x3dom.fields.SFMatrix4f.prototype.norm1_3x3 = function ()
{
var max = Math.abs( this._00 ) +
Math.abs( this._10 ) +
Math.abs( this._20 );
var t = 0;
if ( ( t = Math.abs( this._01 ) +
Math.abs( this._11 ) +
Math.abs( this._21 ) ) > max )
{
max = t;
}
if ( ( t = Math.abs( this._02 ) +
Math.abs( this._12 ) +
Math.abs( this._22 ) ) > max )
{
max = t;
}
return max;
};
/**
* Returns the infinity-norm of the upper left 3x3 part of this matrix.
* The infinity-norm is also known as maximum absolute row sum norm.
*
* @returns {Number} the resulting number
*/
x3dom.fields.SFMatrix4f.prototype.normInf_3x3 = function ()
{
var max = Math.abs( this._00 ) +
Math.abs( this._01 ) +
Math.abs( this._02 );
var t = 0;
if ( ( t = Math.abs( this._10 ) +
Math.abs( this._11 ) +
Math.abs( this._12 ) ) > max )
{
max = t;
}
if ( ( t = Math.abs( this._20 ) +
Math.abs( this._21 ) +
Math.abs( this._22 ) ) > max )
{
max = t;
}
return max;
};
/**
* Computes the transposed adjoint of the upper left 3x3 part of this
* matrix, and stores it in the upper left part of a new 4x4 identity
* matrix.
*
* @returns {x3dom.fields.SFMatrix4f} the resulting matrix
*/
x3dom.fields.SFMatrix4f.prototype.adjointT_3x3 = function ()
{
var result = x3dom.fields.SFMatrix4f.identity();
result._00 = this._11 * this._22 - this._12 * this._21;
result._01 = this._12 * this._20 - this._10 * this._22;
result._02 = this._10 * this._21 - this._11 * this._20;
result._10 = this._21 * this._02 - this._22 * this._01;
result._11 = this._22 * this._00 - this._20 * this._02;
result._12 = this._20 * this._01 - this._21 * this._00;
result._20 = this._01 * this._12 - this._02 * this._11;
result._21 = this._02 * this._10 - this._00 * this._12;
result._22 = this._00 * this._11 - this._01 * this._10;
return result;
};
/**
* Checks whether this matrix equals another matrix.
*
* @param {x3dom.fields.SFMatrix4f} that - the other matrix
* @returns {Boolean}
*/
x3dom.fields.SFMatrix4f.prototype.equals = function ( that )
{
var eps = 0.000000000001;
return Math.abs( this._00 - that._00 ) < eps && Math.abs( this._01 - that._01 ) < eps &&
Math.abs( this._02 - that._02 ) < eps && Math.abs( this._03 - that._03 ) < eps &&
Math.abs( this._10 - that._10 ) < eps && Math.abs( this._11 - that._11 ) < eps &&
Math.abs( this._12 - that._12 ) < eps && Math.abs( this._13 - that._13 ) < eps &&
Math.abs( this._20 - that._20 ) < eps && Math.abs( this._21 - that._21 ) < eps &&
Math.abs( this._22 - that._22 ) < eps && Math.abs( this._23 - that._23 ) < eps &&
Math.abs( this._30 - that._30 ) < eps && Math.abs( this._31 - that._31 ) < eps &&
Math.abs( this._32 - that._32 ) < eps && Math.abs( this._33 - that._33 ) < eps;
};
/**
* Decomposes the matrix into a translation, rotation, scale,
* and scale orientation. Any projection information is discarded.
* The decomposition depends upon choice of center point for rotation
* and scaling, which is optional as the last parameter.
*
* @param {x3dom.fields.SFVec3f} translation -
* 3D vector to be filled with the translation values
* @param {x3dom.fields.Quaternion} rotation -
* quaternion to be filled with the rotation values
* @param {x3dom.fields.SFVec3f} scaleFactor -
* 3D vector to be filled with the scale factors
* @param {x3dom.fields.Quaternion} scaleOrientation -
* rotation (quaternion) to be applied before scaling
* @param {x3dom.fields.SFVec3f} [center=undefined] -
* center point for rotation and scaling, if not origin
*/
x3dom.fields.SFMatrix4f.prototype.getTransform = function (
translation, rotation, scaleFactor, scaleOrientation, center )
{
var m = null;
if ( arguments.length > 4 )
{
m = x3dom.fields.SFMatrix4f.translation( center.negate() );
m = m.mult( this );
var c = x3dom.fields.SFMatrix4f.translation( center );
m = m.mult( c );
}
else
{
m = x3dom.fields.SFMatrix4f.copy( this );
}
var flip = m.decompose( translation, rotation, scaleFactor,
scaleOrientation );
scaleFactor.setValues( scaleFactor.multiply( flip ) );
};
/**
* Computes the decomposition of the given 4x4 affine matrix M as
* M = T F R SO S SO^t, where T is a translation matrix,
* F is +/- I (a reflection), R is a rotation matrix,
* SO is a rotation matrix and S is a (nonuniform) scale matrix.
*
* @param {x3dom.fields.SFVec3f} t -
* 3D vector to be filled with the translation values
* @param {x3dom.fields.Quaternion} r -
* quaternion to be filled with the rotation values
* @param {x3dom.fields.SFVec3f} s -
* 3D vector to be filled with the scale factors
* @param {x3dom.fields.Quaternion} so -
* rotation (quaternion) to be applied before scaling
* @returns {Number} signum of determinant of the transposed adjoint
* upper 3x3 matrix
*/
x3dom.fields.SFMatrix4f.prototype.decompose = function ( t, r, s, so )
{
var A = x3dom.fields.SFMatrix4f.copy( this );
var Q = x3dom.fields.SFMatrix4f.identity(),
S = x3dom.fields.SFMatrix4f.identity(),
SO = x3dom.fields.SFMatrix4f.identity();
t.x = A._03;
t.y = A._13;
t.z = A._23;
A._03 = 0.0;
A._13 = 0.0;
A._23 = 0.0;
A._30 = 0.0;
A._31 = 0.0;
A._32 = 0.0;
var det = A.polarDecompose( Q, S );
var f = 1.0;
if ( det < 0.0 )
{
Q = Q.negate();
f = -1.0;
}
r.setValue( Q );
S.spectralDecompose( SO, s );
so.setValue( SO );
return f;
};
/**
* Performs a polar decomposition of this matrix A into two matrices
* Q and S, so that A = QS.
*
* @param {x3dom.fields.SFMatrix4f} Q - first resulting matrix
* @param {x3dom.fields.SFMatrix4f} S - first resulting matrix
* @returns {Number} determinant of the transposed adjoint upper 3x3
* matrix
*/
x3dom.fields.SFMatrix4f.prototype.polarDecompose = function ( Q, S )
{
var TOL = 0.000000000001;
var Mk = this.transpose();
var Ek = x3dom.fields.SFMatrix4f.identity();
var Mk_one = Mk.norm1_3x3();
var Mk_inf = Mk.normInf_3x3();
var MkAdjT,
MkAdjT_one,
MkAdjT_inf,
Ek_one,
Mk_det;
do
{
// compute transpose of adjoint
MkAdjT = Mk.adjointT_3x3();
// Mk_det = det(Mk) -- computed from the adjoint
Mk_det = Mk._00 * MkAdjT._00 +
Mk._01 * MkAdjT._01 +
Mk._02 * MkAdjT._02;
// TODO: should this be a close to zero test ?
if ( Mk_det == 0.0 )
{
x3dom.debug.logWarning( "polarDecompose: Mk_det == 0.0" );
break;
}
MkAdjT_one = MkAdjT.norm1_3x3();
MkAdjT_inf = MkAdjT.normInf_3x3();
// compute update factors
var gamma = Math.sqrt( Math.sqrt( ( MkAdjT_one * MkAdjT_inf ) /
( Mk_one * Mk_inf ) ) / Math.abs( Mk_det ) );
var g1 = 0.5 * gamma;
var g2 = 0.5 / ( gamma * Mk_det );
Ek.setValues( Mk );
Mk = Mk.multiply( g1 ); // this does:
Mk = Mk.addScaled( MkAdjT, g2 ); // Mk = g1 * Mk + g2 * MkAdjT
Ek = Ek.addScaled( Mk, -1.0 ); // Ek -= Mk;
Ek_one = Ek.norm1_3x3();
Mk_one = Mk.norm1_3x3();
Mk_inf = Mk.normInf_3x3();
} while ( Ek_one > ( Mk_one * TOL ) );
Q.setValues( Mk.transpose() );
S.setValues( Mk.mult( this ) );
for ( var i = 0; i < 3; ++i )
{
for ( var j = i; j < 3; ++j )
{
S.setAt( j, i, 0.5 * ( S.at( j, i ) + S.at( i, j ) ) );
S.setAt( i, j, 0.5 * ( S.at( j, i ) + S.at( i, j ) ) );
}
}
return Mk_det;
};
/**
* Performs a spectral decomposition of this matrix.
*
* @param {x3dom.fields.SFMatrix4f} SO - resulting matrix
* @param {x3dom.fields.SFVec3f} k - resulting vector
*/
x3dom.fields.SFMatrix4f.prototype.spectralDecompose = function ( SO, k )
{
var next = [ 1, 2, 0 ];
var maxIterations = 20;
var diag = [ this._00, this._11, this._22 ];
var offDiag = [ this._12, this._20, this._01 ];
for ( var iter = 0; iter < maxIterations; ++iter )
{
var sm = Math.abs( offDiag[ 0 ] )
+ Math.abs( offDiag[ 1 ] )
+ Math.abs( offDiag[ 2 ] );
if ( sm == 0 )
{
break;
}
for ( var i = 2; i >= 0; --i )
{
var p = next[ i ];
var q = next[ p ];
var absOffDiag = Math.abs( offDiag[ i ] );
var g = 100.0 * absOffDiag;
if ( absOffDiag > 0.0 )
{
var t = 0,
h = diag[ q ] - diag[ p ];
var absh = Math.abs( h );
if ( absh + g == absh )
{
t = offDiag[ i ] / h;
}
else
{
var theta = 0.5 * h / offDiag[ i ];
t = 1.0 / ( Math.abs( theta )
+ Math.sqrt( theta * theta + 1.0 ) );
t = theta < 0.0 ? -t : t;
}
var c = 1.0 / Math.sqrt( t * t + 1.0 );
var s = t * c;
var tau = s / ( c + 1.0 );
var ta = t * offDiag[ i ];
offDiag[ i ] = 0.0;
diag[ p ] -= ta;
diag[ q ] += ta;
var offDiagq = offDiag[ q ];
offDiag[ q ] -= s * ( offDiag[ p ] + tau * offDiagq );
offDiag[ p ] += s * ( offDiagq - tau * offDiag[ p ] );
for ( var j = 2; j >= 0; --j )
{
var a = SO.at( j, p );
var b = SO.at( j, q );
SO.setAt( j, p, SO.at( j, p ) - s * ( b + tau * a ) );
SO.setAt( j, q, SO.at( j, q ) + s * ( a - tau * b ) );
}
}
}
}
k.x = diag[ 0 ];
k.y = diag[ 1 ];
k.z = diag[ 2 ];
};
/**
* Computes the logarithm of this matrix, assuming that its determinant
* is greater than zero.
*
* @returns {x3dom.fields.SFMatrix4f} log of matrix
*/
x3dom.fields.SFMatrix4f.prototype.log = function ()
{
var maxiter = 12;
var eps = 1e-12;
var A = x3dom.fields.SFMatrix4f.copy( this );
var Z = x3dom.fields.SFMatrix4f.copy( this );
// Take repeated square roots to reduce spectral radius
Z._00 -= 1;
Z._11 -= 1;
Z._22 -= 1;
Z._33 -= 1;
var k = 0;
while ( Z.normInfinity() > 0.5 )
{
A = A.sqrt();
Z.setValues( A );
Z._00 -= 1;
Z._11 -= 1;
Z._22 -= 1;
Z._33 -= 1;
k++;
}
A._00 -= 1;
A._11 -= 1;
A._22 -= 1;
A._33 -= 1;
A = A.negate();
Z.setValues( A );
var result = x3dom.fields.SFMatrix4f.copy( A );
var i = 1;
while ( Z.normInfinity() > eps && i < maxiter )
{
Z = Z.mult( A );
i++;
result = result.addScaled( Z, 1.0 / i );
}
return result.multiply( -( 1 << k ) );
};
/**
* Computes the exponential of this matrix.
*
* @returns {x3dom.fields.SFMatrix4f} exp of matrix
*/
x3dom.fields.SFMatrix4f.prototype.exp = function ()
{
var q = 6;
var A = x3dom.fields.SFMatrix4f.copy( this ),
D = x3dom.fields.SFMatrix4f.identity(),
N = x3dom.fields.SFMatrix4f.identity(),
result = x3dom.fields.SFMatrix4f.identity();
var k = 0,
c = 1.0;
var j = 1.0 + parseInt( Math.log( A.normInfinity() / 0.693 ) );
// var j = 1.0 + (Math.log(A.normInfinity() / 0.693) | 0);
if ( j < 0 )
{
j = 0;
}
A = A.multiply( 1.0 / ( 1 << j ) );
for ( k = 1; k <= q; k++ )
{
c *= ( q - k + 1 ) / ( k * ( 2 * q - k + 1 ) );
result = A.mult( result );
N = N.addScaled( result, c );
if ( k % 2 )
{
D = D.addScaled( result, -c );
}
else
{
D = D.addScaled( result, c );
}
}
result = D.inverse().mult( N );
for ( k = 0; k < j; k++ )
{
result = result.mult( result );
}
return result;
};
/**
* Computes a determinant for a 3x3 matrix m, given as values in
* row major order.
*
* @param {Number} a1 - value of m at (0,0)
* @param {Number} a2 - value of m at (0,1)
* @param {Number} a3 - value of m at (0,2)
* @param {Number} b1 - value of m at (1,0)
* @param {Number} b2 - value of m at (1,1)
* @param {Number} b3 - value of m at (1,2)
* @param {Number} c1 - value of m at (2,0)
* @param {Number} c2 - value of m at (2,1)
* @param {Number} c3 - value of m at (2,2)
* @returns {Number} determinant
*/
x3dom.fields.SFMatrix4f.prototype.det3 = function ( a1, a2, a3,
b1, b2, b3,
c1, c2, c3 )
{
// return ( ( a1 * b2 * c3 ) + ( a2 * b3 * c1 ) + ( a3 * b1 * c2 ) -
// ( a1 * b3 * c2 ) - ( a2 * b1 * c3 ) - ( a3 * b2 * c1 ) );
return a1 * ( b2 * c3 - b3 * c2 ) +
a2 * ( b3 * c1 - b1 * c3 ) +
a3 * ( b1 * c2 - b2 * c1 );
};
/**
* Computes the determinant of this matrix.
*
* @returns {Number} determinant
*/
x3dom.fields.SFMatrix4f.prototype.det = function ()
{
var a1 = this._00;
var b1 = this._10;
var c1 = this._20;
var d1 = this._30;
var a2 = this._01;
var b2 = this._11;
var c2 = this._21;
var d2 = this._31;
var a3 = this._02;
var b3 = this._12;
var c3 = this._22;
var d3 = this._32;
var a4 = this._03;
var b4 = this._13;
var c4 = this._23;
var d4 = this._33;
return ( a1 * this.det3( b2, b3, b4, c2, c3, c4, d2, d3, d4 ) -
b1 * this.det3( a2, a3, a4, c2, c3, c4, d2, d3, d4 ) +
c1 * this.det3( a2, a3, a4, b2, b3, b4, d2, d3, d4 ) -
d1 * this.det3( a2, a3, a4, b2, b3, b4, c2, c3, c4 ) );
};
/**
* Computes the inverse of this matrix, given that it is not singular.
*
* @returns {x3dom.fields.SFMatrix4f} the inverse of this matrix
*/
x3dom.fields.SFMatrix4f.prototype.inverse = function ()
{
var a1 = this._00;
var b1 = this._10;
var c1 = this._20;
var d1 = this._30;
var a2 = this._01;
var b2 = this._11;
var c2 = this._21;
var d2 = this._31;
var a3 = this._02;
var b3 = this._12;
var c3 = this._22;
var d3 = this._32;
var a4 = this._03;
var b4 = this._13;
var c4 = this._23;
var d4 = this._33;
var rDet = this.det();
// if (Math.abs(rDet) < 1e-30)
if ( rDet == 0 )
{
x3dom.debug.logWarning( "Invert matrix: singular matrix, " +
"no inverse!" );
return x3dom.fields.SFMatrix4f.identity();
}
rDet = 1.0 / rDet;
return new x3dom.fields.SFMatrix4f(
+this.det3( b2, b3, b4, c2, c3, c4, d2, d3, d4 ) * rDet,
-this.det3( a2, a3, a4, c2, c3, c4, d2, d3, d4 ) * rDet,
+this.det3( a2, a3, a4, b2, b3, b4, d2, d3, d4 ) * rDet,
-this.det3( a2, a3, a4, b2, b3, b4, c2, c3, c4 ) * rDet,
-this.det3( b1, b3, b4, c1, c3, c4, d1, d3, d4 ) * rDet,
+this.det3( a1, a3, a4, c1, c3, c4, d1, d3, d4 ) * rDet,
-this.det3( a1, a3, a4, b1, b3, b4, d1, d3, d4 ) * rDet,
+this.det3( a1, a3, a4, b1, b3, b4, c1, c3, c4 ) * rDet,
+this.det3( b1, b2, b4, c1, c2, c4, d1, d2, d4 ) * rDet,
-this.det3( a1, a2, a4, c1, c2, c4, d1, d2, d4 ) * rDet,
+this.det3( a1, a2, a4, b1, b2, b4, d1, d2, d4 ) * rDet,
-this.det3( a1, a2, a4, b1, b2, b4, c1, c2, c4 ) * rDet,
-this.det3( b1, b2, b3, c1, c2, c3, d1, d2, d3 ) * rDet,
+this.det3( a1, a2, a3, c1, c2, c3, d1, d2, d3 ) * rDet,
-this.det3( a1, a2, a3, b1, b2, b3, d1, d2, d3 ) * rDet,
+this.det3( a1, a2, a3, b1, b2, b3, c1, c2, c3 ) * rDet
);
};
/**
* Returns an array of 2*3 = 6 euler angles (in radians), assuming that
* this is a rotation matrix. The first three and the second three
* values are alternatives for the three euler angles, where each of the
* two cases leads to the same resulting rotation.
*
* @returns {Number[]} an array of 6 Euler angles in radians, each
* consecutive triple of which represents one of the
* two alternative choices for the same rotation
*/
x3dom.fields.SFMatrix4f.prototype.getEulerAngles = function ()
{
var theta_1,
theta_2,
theta,
phi_1,
phi_2,
phi,
psi_1,
psi_2,
psi,
cos_theta_1,
cos_theta_2;
if ( Math.abs( ( Math.abs( this._20 ) - 1.0 ) ) > 0.0001 )
{
theta_1 = -Math.asin( this._20 );
theta_2 = Math.PI - theta_1;
cos_theta_1 = Math.cos( theta_1 );
cos_theta_2 = Math.cos( theta_2 );
psi_1 = Math.atan2( this._21 / cos_theta_1, this._22 / cos_theta_1 );
psi_2 = Math.atan2( this._21 / cos_theta_2, this._22 / cos_theta_2 );
phi_1 = Math.atan2( this._10 / cos_theta_1, this._00 / cos_theta_1 );
phi_2 = Math.atan2( this._10 / cos_theta_2, this._00 / cos_theta_2 );
return [ psi_1, theta_1, phi_1,
psi_2, theta_2, phi_2 ];
}
else
{
phi = 0;
if ( this._20 == -1.0 )
{
theta = Math.PI / 2.0;
psi = phi + Math.atan2( this._01, this._02 );
}
else
{
theta = -( Math.PI / 2.0 );
psi = -phi + Math.atan2( -this._01, -this._02 );
}
return [ psi, theta, phi,
psi, theta, phi ];
}
};
/**
* Converts this matrix to a string representation, where all entries
* are separated by whitespaces.
*
* @returns {String} a string representation of this matrix
*/
x3dom.fields.SFMatrix4f.prototype.toString = function ()
{
return this._00.toFixed( 6 ) + " " + this._10.toFixed( 6 ) + " " +
this._20.toFixed( 6 ) + " " + this._30.toFixed( 6 ) + " " +
this._01.toFixed( 6 ) + " " + this._11.toFixed( 6 ) + " " +
this._21.toFixed( 6 ) + " " + this._31.toFixed( 6 ) + " " +
this._02.toFixed( 6 ) + " " + this._12.toFixed( 6 ) + " " +
this._22.toFixed( 6 ) + " " + this._32.toFixed( 6 ) + " " +
this._03.toFixed( 6 ) + " " + this._13.toFixed( 6 ) + " " +
this._23.toFixed( 6 ) + " " + this._33.toFixed( 6 );
};
/**
* Fills the values of this matrix from a string, where the entries are
* separated by commas or whitespace, and given in column-major order.
*
* @param {String} str - the string representation
*/
x3dom.fields.SFMatrix4f.prototype.setValueByStr = function ( str )
{
var needTranspose = false;
var val = /matrix.*\((.+)\)/;
// check if matrix is set via CSS string
if ( val.exec( str ) )
{
str = RegExp.$1;
needTranspose = true;
}
var arr = str.split( /[,\s]+/ ).map( function ( n ){ return +n; } );
if ( arr.length >= 16 )
{
if ( !needTranspose )
{
this._00 = arr[ 0 ]; this._01 = arr[ 1 ]; this._02 = arr[ 2 ]; this._03 = arr[ 3 ];
this._10 = arr[ 4 ]; this._11 = arr[ 5 ]; this._12 = arr[ 6 ]; this._13 = arr[ 7 ];
this._20 = arr[ 8 ]; this._21 = arr[ 9 ]; this._22 = arr[ 10 ]; this._23 = arr[ 11 ];
this._30 = arr[ 12 ]; this._31 = arr[ 13 ]; this._32 = arr[ 14 ]; this._33 = arr[ 15 ];
}
else
{
this._00 = arr[ 0 ]; this._01 = arr[ 4 ]; this._02 = arr[ 8 ]; this._03 = arr[ 12 ];
this._10 = arr[ 1 ]; this._11 = arr[ 5 ]; this._12 = arr[ 9 ]; this._13 = arr[ 13 ];
this._20 = arr[ 2 ]; this._21 = arr[ 6 ]; this._22 = arr[ 10 ]; this._23 = arr[ 14 ];
this._30 = arr[ 3 ]; this._31 = arr[ 7 ]; this._32 = arr[ 11 ]; this._33 = arr[ 15 ];
}
}
else if ( arr.length === 6 )
{
this._00 = arr[ 0 ]; this._01 = arr[ 1 ]; this._02 = 0; this._03 = arr[ 4 ];
this._10 = arr[ 2 ]; this._11 = arr[ 3 ]; this._12 = 0; this._13 = arr[ 5 ];
this._20 = 0; this._21 = 0; this._22 = 1; this._23 = 0;
this._30 = 0; this._31 = 0; this._32 = 0; this._33 = 1;
}
else
{
x3dom.debug.logWarning( "SFMatrix4f - can't parse string: " + str );
}
return this;
};
/**
* SFVec2f constructor.
*
* Represents a two-dimensional vector, the components of which, all
* being numbers, can be accessed and modified directly.
*
* @class Represents a SFVec2f
*/
x3dom.fields.SFVec2f = function ( x, y )
{
if ( arguments.length === 0 )
{
this.x = 0;
this.y = 0;
}
else
{
this.x = x;
this.y = y;
}
};
/**
* Returns a copy of the given 2D vector.
*
* @param {x3dom.fields.SFVec2f} v - the vector to copy
* @returns {x3dom.fields.SFVec2f} the copy of the input vector
*/
x3dom.fields.SFVec2f.copy = function ( v )
{
return new x3dom.fields.SFVec2f( v.x, v.y );
};
x3dom.fields.SFVec2f.parse = function ( str )
{
var m = /^\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*$/.exec( str );
if ( m === null ) {return new x3dom.fields.SFVec2f();}
return new x3dom.fields.SFVec2f( +m[ 1 ], +m[ 2 ] );
};
/**
* Returns a copy of this 2D vector.
*
* @returns {x3dom.fields.SFVec2f} the copy of this vector
*/
x3dom.fields.SFVec2f.prototype.copy = function ()
{
return x3dom.fields.SFVec2f.copy( this );
};
/**
* Sets this vector's components from another vector, and returns this
* modified vector.
*
* @param {x3dom.fields.SFVec2f} that - the vector to copy from
* @returns {x3dom.fields.SFVec2f} this modified vector
*/
x3dom.fields.SFVec2f.prototype.setValues = function ( that )
{
this.x = that.x;
this.y = that.y;
return this;
};
/**
* Returns the vector component at the given index.
*
* @param {Number} i - the index of the vector component to obtain,
* with 0 being the x-coordinate, 1 being the
* y-coordinate, and any other value defaulting to
* the x-coordinate
* @returns {Number} the vector component at the specified index
*/
x3dom.fields.SFVec2f.prototype.at = function ( i )
{
switch ( i )
{
case 0: return this.x;
case 1: return this.y;
default: return this.x;
}
};
/**
* Returns a new vector as the sum of adding another vector to this one.
*
* @param {x3dom.fields.SFVec2f} that - the vector to add to this
* vector
* @returns {x3dom.fields.SFVec2f} a new vector holding the sum
*/
x3dom.fields.SFVec2f.prototype.add = function ( that )
{
return new x3dom.fields.SFVec2f( this.x + that.x, this.y + that.y );
};
/**
* Returns a new vector as the difference between this vector and
* another one subtracted from it.
*
* @param {x3dom.fields.SFVec2f} that - the vector to deduct from this
* vector
* @returns {x3dom.fields.SFVec2f} a new vector holding the difference
*/
x3dom.fields.SFVec2f.prototype.subtract = function ( that )
{
return new x3dom.fields.SFVec2f( this.x - that.x, this.y - that.y );
};
/**
* Returns a negated version of this vector.
*
* @returns {x3dom.fields.SFVec2f} a negated version of this vector
*/
x3dom.fields.SFVec2f.prototype.negate = function ()
{
return new x3dom.fields.SFVec2f( -this.x, -this.y );
};
/**
* Returns the dot product between this vector and another one.
*
* @param {x3dom.fields.SFVec2f} that - the right-hand side vector
* @returns {Number} the dot product between this and the other vector
*/
x3dom.fields.SFVec2f.prototype.dot = function ( that )
{
return this.x * that.x + this.y * that.y;
};
/**
* Returns the vector obtained by reflecting this vector at another one.
*
* @param {x3dom.fields.SFVec2f} n - the vector to reflect this one at
* @returns {x3dom.fields.SFVec2f} the reflection vector
*/
x3dom.fields.SFVec2f.prototype.reflect = function ( n )
{
var d2 = this.dot( n ) * 2;
return new x3dom.fields.SFVec2f( this.x - d2 * n.x, this.y - d2 * n.y );
};
/**
* Returns a normalized version of this vector.
*
* If this vector's magnitude equals zero, the resulting will be the
* zero vector.
*
* @returns {x3dom.fields.SFVec2f} a new normalized vector based on
* this one
*/
x3dom.fields.SFVec2f.prototype.normalize = function ()
{
var n = this.length();
if ( n ) { n = 1.0 / n; }
return new x3dom.fields.SFVec2f( this.x * n, this.y * n );
};
/**
* Returns the product of the componentwise multiplication of this
* vector with another one.
*
* @param {x3dom.fields.SFVec2f} that - the multiplicand (right) vector
* @returns {x3dom.fields.SFVec2f} a new vector as product of a
* componentwise multiplication of this
* vector with the other one
*/
x3dom.fields.SFVec2f.prototype.multComponents = function ( that )
{
return new x3dom.fields.SFVec2f( this.x * that.x, this.y * that.y );
};
/**
* Returns a scaled version of this vector.
*
* @param {Number} n - the scalar scaling factor for this vector
* @returns {x3dom.fields.SFVec2f} a new vector obtained by scaling this
* one by the given factor
*/
x3dom.fields.SFVec2f.prototype.multiply = function ( n )
{
return new x3dom.fields.SFVec2f( this.x * n, this.y * n );
};
/**
* Returns the quotient of this vector componentwise divided by another
* one.
*
* @param {x3dom.fields.SFVec2f} that - the divisor vector to divide by
* @returns {x3dom.fields.SFVec2f} the quotient obtained by componentwise
* division of this vector by the other
*/
x3dom.fields.SFVec2f.prototype.divideComponents = function ( that )
{
return new x3dom.fields.SFVec2f( this.x / that.x, this.y / that.y );
};
/**
* Returns a version of this vector with its components divided by
* the scalar factor.
*
* @param {Number} n - the scalar factor to divide the components by
* @returns {x3dom.fields.SFVec2f} a new vector obtained by dividing
* this one's components by the scalar
*/
x3dom.fields.SFVec2f.prototype.divide = function ( n )
{
var denom = n ? ( 1.0 / n ) : 1.0;
return new x3dom.fields.SFVec2f( this.x * denom, this.y * denom );
};
/**
* Checks whether this vector equals another one, as defined by the
* deviation tolerance among their components.
*
* @param {x3dom.fields.SFVec2f} that - the vector to compare to this one
* @param {Number} eps - the tolerance of deviation
* within which not exactly equal
* components are still considered
* equal
* @returns {Boolean} ``true'' if both vectors are equal or
* approximately equal, ``false'' otherwise
*/
x3dom.fields.SFVec2f.prototype.equals = function ( that, eps )
{
return Math.abs( this.x - that.x ) < eps &&
Math.abs( this.y - that.y ) < eps;
};
/**
* Returns the length, or magnitude, of this vector.
*
* @returns {Number} the magnitude of this vector
*/
x3dom.fields.SFVec2f.prototype.length = function ()
{
return Math.sqrt( ( this.x * this.x ) + ( this.y * this.y ) );
};
/**
* Returns the components of this vector as an array of two numbers,
* suitable for interaction with OpenGL.
*
* @returns {Number[]} an array holding this vector's x- and
* y-coordinates in this order
*/
x3dom.fields.SFVec2f.prototype.toGL = function ()
{
return [ this.x, this.y ];
};
/**
* Returns a string representation of this vector.
*
* @returns {String} a string representation of this vector
*/
x3dom.fields.SFVec2f.prototype.toString = function ()
{
return this.x + " " + this.y;
};
/**
* Parses a string and sets this vector's components to the values
* obtained from it, returning this modified vector.
*
* @param {String} str - the string to parse the coordinates from
* @returns {x3dom.fields.SFVec2f} this modified vector
*/
x3dom.fields.SFVec2f.prototype.setValueByStr = function ( str )
{
var m = /^\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*$/.exec( str );
m = m || [ 0, 0, 0 ];
this.x = +m[ 1 ];
this.y = +m[ 2 ];
return this;
};
/**
* SFVec3f constructor.
*
* Represents a three-dimensional vector, the components of which, all
* being numbers, can be accessed and modified directly.
*
* @class Represents a SFVec3f
*/
x3dom.fields.SFVec3f = function ( x, y, z )
{
if ( arguments.length === 0 )
{
this.x = 0;
this.y = 0;
this.z = 0;
}
else
{
this.x = x;
this.y = y;
this.z = z;
}
};
/**
* The 3D zero vector.
*/
x3dom.fields.SFVec3f.NullVector = new x3dom.fields.SFVec3f( 0, 0, 0 );
/**
* A 3D vector whose components all equal one.
*/
x3dom.fields.SFVec3f.OneVector = new x3dom.fields.SFVec3f( 1, 1, 1 );
/**
* Returns a copy of the supplied 3D vector.
*
* @param {x3dom.fields.SFVec3f} v - the vector to copy
* @returns {x3dom.fields.SFVec3f} a copy of the input vector
*/
x3dom.fields.SFVec3f.copy = function ( v )
{
return new x3dom.fields.SFVec3f( v.x, v.y, v.z );
};
/**
* Returns a vector whose components are set to the smallest
* representable value.
*
* @returns {x3dom.fields.SFVec3f} a vector with the smallest
* representable value for each component
*/
x3dom.fields.SFVec3f.MIN = function ()
{
return new x3dom.fields.SFVec3f( -Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE );
};
/**
* Returns a vector whose components are set to the largest
* representable value.
*
* @returns {x3dom.fields.SFVec3f} a vector with the largest
* representable value for each component
*/
x3dom.fields.SFVec3f.MAX = function ()
{
return new x3dom.fields.SFVec3f( Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE );
};
/**
* Parses and returns a vector from a string representation.
*
* @param {String} str - the string to parse the vector data from
* @returns {x3dom.fields.SFVec3f} a new vector parsed from the string
*/
x3dom.fields.SFVec3f.parse = function ( str )
{
try
{
var m = /^\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*$/.exec( str );
return new x3dom.fields.SFVec3f( +m[ 1 ], +m[ 2 ], +m[ 3 ] );
}
catch ( e )
{
// allow automatic type conversion as is convenient for shaders
var c = x3dom.fields.SFColor.colorParse( str );
return new x3dom.fields.SFVec3f( c.r, c.g, c.b );
}
};
/**
* Returns a copy of this vector.
*
* @returns {x3dom.fields.SFVec3f} a copy of this vector
*/
x3dom.fields.SFVec3f.prototype.copy = function ()
{
return x3dom.fields.SFVec3f.copy( this );
};
/**
* Sets the components of this vector from the supplied array.
*
* @param {Number[]} array - an array of at least three numbers, the
* first three of which will become this
* vector's x-, y-, and z-coordinates
* @returns {x3dom.fields.SFVec3f} this modified vector
*/
x3dom.fields.SFVec3f.prototype.fromArray = function ( array )
{
this.x = array[ 0 ];
this.y = array[ 1 ];
this.z = array[ 2 ];
return this;
};
/**
* Creates and returns a new 3D vector containin the elements of the
* input array.
*
* @param {Number[]} array - an array of at least three numbers, the
* first three of which will become the new
* vector's x-, y-, and z-coordinates
* @returns {x3dom.fields.SFVec3f} a new vector with the input array's
* values
*/
x3dom.fields.SFVec3f.fromArray = function ( array )
{
return new x3dom.fields.SFVec3f( array[ 0 ], array[ 1 ], array[ 2 ] );
};
/**
* Sets the components of this vector from another vector, and returns
* this modified vector.
*
* @param {x3dom.fields.SFVec3f} that - the vector to copy from
* @returns {x3dom.fields.SFVec3f} this modified vector
*/
x3dom.fields.SFVec3f.prototype.setValues = function ( that )
{
this.x = that.x;
this.y = that.y;
this.z = that.z;
return this;
};
/**
* Sets the components of this vector from supplied coordinates, and
* returns this modified vector.
*
* @param {Number} x - the new x-coordinate
* @param {Number} y - the new y-coordinate
* @param {Number} z - the new z-coordinate
* @returns {x3dom.fields.SFVec3f} this modified vector
*/
x3dom.fields.SFVec3f.prototype.set = function ( x, y, z )
{
this.x = x;
this.y = y;
this.z = z;
return this;
};
/**
* Returns the vector component with at the given index.
*
* If the index does not designate a valid component, per default the
* x-coordinate is returned.
*
* @param {Number} i - the index of the vector component to access,
* starting at zero
* @returns {Number} the vector component at the given index, or the
* x-component if invalid
*/
x3dom.fields.SFVec3f.prototype.at = function ( i )
{
switch ( i )
{
case 0: return this.x;
case 1: return this.y;
case 2: return this.z;
default: return this.x;
}
};
/**
* Returns a new vector as the sum of adding another vector to this one.
*
* @param {x3dom.fields.SFVec3f} that - the vector to add to this*
vector
* @returns {x3dom.fields.SFVec3f} a new vector holding the sum
*/
x3dom.fields.SFVec3f.prototype.add = function ( that )
{
return new x3dom.fields.SFVec3f( this.x + that.x, this.y + that.y, this.z + that.z );
};
/**
* Returns a new vector as the sum of adding another scaled vector,
* scaled by the supplied scalar value, to this one.
*
* @param {x3dom.fields.SFVec3f} that - the vector to scale and add to
* this one; will not be modified
* @param {Number} s - the factor to scale the
* ``that'' vector by
* before the addition
* @returns {x3dom.fields.SFVec3f} a new vector holding the sum
*/
x3dom.fields.SFVec3f.prototype.addScaled = function ( that, s )
{
return new x3dom.fields.SFVec3f( this.x + s * that.x, this.y + s * that.y, this.z + s * that.z );
};
/**
* Returns a new vector as the difference between this vector and
* another one subtracted from it.
*
* @param {x3dom.fields.SFVec3f} that - the vector to deduct from this
* vector
* @returns {x3dom.fields.SFVec3f} a new vector holding the difference
*/
x3dom.fields.SFVec3f.prototype.subtract = function ( that )
{
return new x3dom.fields.SFVec3f( this.x - that.x, this.y - that.y, this.z - that.z );
};
/**
* Returns a new vector as the difference between two vectors.
*
* @param {x3dom.fields.SFVec3f} a - the vector to be deducted by the
* second one
* @param {x3dom.fields.SFVec3f} b - the vector to deduct from the
* first one
* @returns {x3dom.fields.SFVec3f} a new vector holding the difference
*/
x3dom.fields.SFVec3f.prototype.subtractVectors = function ( a, b )
{
return new x3dom.fields.SFVec3f( a.x - b.x, a.y - b.y, a.z - b.z );
};
/**
* Returns a version of this vector with all components negated.
*
* @returns {x3dom.fields.SFVec3f} a negated version of this vector
*/
x3dom.fields.SFVec3f.prototype.negate = function ()
{
return new x3dom.fields.SFVec3f( -this.x, -this.y, -this.z );
};
/**
* Returns the dot product between this vector and another one.
*
* @param {x3dom.fields.SFVec3f} that - the right-hand side vector
* @returns {Number} the dot product between this vector and the other one
*/
x3dom.fields.SFVec3f.prototype.dot = function ( that )
{
return ( this.x * that.x + this.y * that.y + this.z * that.z );
};
/**
* Returns a new vector representing the cross product between this
* vector and another one.
*
* @param {x3dom.fields.SFVec3f} that - the right-handside vector
* @returns {x3dom.fields.SFVec3f} the cross product of the two vectors
*/
x3dom.fields.SFVec3f.prototype.cross = function ( that )
{
return new x3dom.fields.SFVec3f( this.y * that.z - this.z * that.y,
this.z * that.x - this.x * that.z,
this.x * that.y - this.y * that.x );
};
/**
* Returns the vector obtained by reflecting this vector at another one.
*
* @param {x3dom.fields.SFVec3f} n - the vector to reflect this one at
* @returns {x3dom.fields.SFVec3f} the reflection vector
*/
x3dom.fields.SFVec3f.prototype.reflect = function ( n )
{
var d2 = this.dot( n ) * 2;
return new x3dom.fields.SFVec3f( this.x - d2 * n.x,
this.y - d2 * n.y,
this.z - d2 * n.z );
};
/**
* Returns the magnitude, or length, of this vector.
*
* @returns {Number} the magnitude of this vector
*/
x3dom.fields.SFVec3f.prototype.length = function ()
{
return Math.sqrt( ( this.x * this.x ) +
( this.y * this.y ) +
( this.z * this.z ) );
};
/**
* Returns a normalized version of this vector.
*
* @returns {x3dom.fields.SFVec3f} a normalized version of this vector
*/
x3dom.fields.SFVec3f.prototype.normalize = function ()
{
var n = this.length();
if ( n ) { n = 1.0 / n; }
return new x3dom.fields.SFVec3f( this.x * n, this.y * n, this.z * n );
};
/**
* Returns a new vector obtained by componentwise multiplication of
* this one with another vector.
*
* @param {x3dom.fields.SFVec3f} that - the vector whose components
* shall be multiplied with those
* of this vector
* @returns {x3dom.fields.SFVec3f} the vector containing the
* componentwise multiplication product
* of this and the supplied vector
*/
x3dom.fields.SFVec3f.prototype.multComponents = function ( that )
{
return new x3dom.fields.SFVec3f( this.x * that.x, this.y * that.y, this.z * that.z );
};
/**
* Returns a new vector obtained by scaling this vector by the supplied
* scalar factor.
*
* @param {Number} n - the scalar scaling factor for this vector
* @returns {x3dom.fields.SFVec3f} a scaled version of this vector
*/
x3dom.fields.SFVec3f.prototype.multiply = function ( n )
{
return new x3dom.fields.SFVec3f( this.x * n, this.y * n, this.z * n );
};
/**
* Returns a new vector obtained by dividing this vector by the supplied
* scalar factor.
*
* @param {Number} n - the scalar division factor for this vector
* @returns {x3dom.fields.SFVec3f} a scaled version of this vector
*/
x3dom.fields.SFVec3f.prototype.divide = function ( n )
{
var denom = n ? ( 1.0 / n ) : 1.0;
return new x3dom.fields.SFVec3f( this.x * denom, this.y * denom, this.z * denom );
};
/**
* Checks whether this vector equals another one, as defined by the
* deviation tolerance among their components.
*
* @param {x3dom.fields.SFVec3f} that - the vector to compare to this one
* @param {Number} eps - the tolerance of deviation
* within which not exactly equal
* components are still considered
* equal
* @returns {Boolean} ``true'' if both vectors are equal or
* approximately equal, ``false'' otherwise
*/
x3dom.fields.SFVec3f.prototype.equals = function ( that, eps )
{
return Math.abs( this.x - that.x ) < eps &&
Math.abs( this.y - that.y ) < eps &&
Math.abs( this.z - that.z ) < eps;
};
/**
* Returns a four-element array holding this vector's components in a
* mode suitable for interaction with OpenGL.
*
* @returns {Number[]} an array with this vector's x-, y-,
* and z-coordinates in this order
*/
x3dom.fields.SFVec3f.prototype.toGL = function ()
{
return [ this.x, this.y, this.z ];
};
/**
* Returns a string representation of this vector.
*
* @returns {String} a string representation of this vector
*/
x3dom.fields.SFVec3f.prototype.toString = function ()
{
return this.x + " " + this.y + " " + this.z;
};
/**
* Parses a string, sets this vector's components from the parsed data,
* and returns this vector.
*
* @param {String} str - the string to parse
* @returns {x3dom.fields.SFVec3f} this modified vector
*/
x3dom.fields.SFVec3f.prototype.setValueByStr = function ( str )
{
try
{
var m = /^\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*$/.exec( str );
this.x = +m[ 1 ];
this.y = +m[ 2 ];
this.z = +m[ 3 ];
}
catch ( e )
{
// allow automatic type conversion as is convenient for shaders
var c = x3dom.fields.SFColor.colorParse( str );
this.x = c.r;
this.y = c.g;
this.z = c.b;
}
return this;
};
/**
* SFVec4f constructor.
*
* Represents a four-dimensional vector, the components of which, all
* being numbers, can be accessed and modified directly.
*
* @class Represents a SFVec4f
*/
x3dom.fields.SFVec4f = function ( x, y, z, w )
{
if ( arguments.length === 0 )
{
this.x = 0;
this.y = 0;
this.z = 0;
this.w = 0;
}
else
{
this.x = x;
this.y = y;
this.z = z;
this.w = w;
}
};
/**
* Returns a copy of the given vector.
*
* @param {x3dom.fields.SFVec4f} v - the vector to copy
* @returns {x3dom.fields.SFVec4f} a copy of the input vector
*/
x3dom.fields.SFVec4f.copy = function ( v )
{
return new x3dom.fields.SFVec4f( v.x, v.y, v.z, v.w );
};
/**
* Returns a copy of this vector.
*
* @returns {x3dom.fields.SFVec4f} a copy of this vector
*/
x3dom.fields.SFVec4f.prototype.copy = function ()
{
return x3dom.fields.SFVec4f( this );
};
/**
* Parses a string and returns a new 4D vector with the parsed
* coordinates.
*
* The input string must contain four numbers to produce a valid result.
*
* @param {String} str - the string to parse
* @returns {x3dom.fields.SFVec3f} a new 4D vector containing the
* parsed coordinates
*/
x3dom.fields.SFVec4f.parse = function ( str )
{
var m = /^\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*$/.exec( str );
if ( m === null ) {return new x3dom.fields.SFVec4f();}
return new x3dom.fields.SFVec4f( +m[ 1 ], +m[ 2 ], +m[ 3 ], +m[ 4 ] );
};
/**
* Parses a string, sets this vector's components from the parsed data,
* and returns this vector.
*
* @param {String} str - the string to parse
* @returns {x3dom.fields.SFVec4f} this modified vector
*/
x3dom.fields.SFVec4f.prototype.setValueByStr = function ( str )
{
var m = /^\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*$/.exec( str );
m = m || [ 0, 0, 0, 0, 0 ];
this.x = +m[ 1 ];
this.y = +m[ 2 ];
this.z = +m[ 3 ];
this.w = +m[ 4 ];
return this;
};
/**
* Returns an OpenGL-conformant array representation of this vector.
*
* @returns {Number[]} the four components of this vector
*/
x3dom.fields.SFVec4f.prototype.toGL = function ()
{
return [ this.x, this.y, this.z, this.w ];
};
/**
* Returns a string representation of this vector.
*
* @returns {String} a string representation of this vector
*/
x3dom.fields.SFVec4f.prototype.toString = function ()
{
return this.x + " " + this.y + " " + this.z + " " + this.w;
};
/**
* Quaternion constructor.
*
* Represents a quaternion, the four components of which, all being
* numbers, can be accessed and modified directly.
*
* Note that the coordinates, if supplied separately or in an array,
* are always construed in the order of x, y, z, and w. This fact is
* significant as some conventions prepend the scalar component w
* instead of listing it as the terminal entity. A quaternion in our
* sense, hence, is a quadruple (x, y, z, w).
*
* @class Represents a Quaternion
*/
x3dom.fields.Quaternion = function ( x, y, z, w )
{
if ( arguments.length === 0 )
{
this.x = 0;
this.y = 0;
this.z = 0;
this.w = 1;
}
else
{
this.x = x;
this.y = y;
this.z = z;
this.w = w;
}
};
/**
* SFRotation constructor.
*
* Represents a SFRotation, the four components of which, all being
* numbers, can be accessed and modified directly.
* Access is by array index, modification by property name or array index.
*
* Instance creation adheres to the X3D SAI spec.
* Four numbers x, y, z, angle as arguments: x, y, and z are the axis of the rotation. angle is the angle of the rotation (in radians). Missing values default to 0.0, except y, which defaults to 1.0.
* One SFVec3f axis, one number angle as arguments: axis is the axis of rotation. angle is the angle of the rotation (in radians)
* Two SFVec3f as arguments: fromVector and toVector are normalized and the rotation value that matches the minimum rotation between the fromVector to the toVector is stored in the object.
* @class Represents a SFRotation
*/
x3dom.fields.SFRotation = new Proxy( x3dom.fields.Quaternion,
{
construct : function ( target, args )
{
args[ 0 ] = args[ 0 ] || 0;
args[ 1 ] = args[ 1 ] == undefined ? 1 : args[ 1 ];
args[ 2 ] = args[ 2 ] || 0;
args[ 3 ] = args[ 3 ] || 0;
var quat;
var handler = {
get : function ( target, prop )
{
switch ( prop )
{
case "0":
//case "x":
return target.SFRotation.x;
break;
case "1":
//case "y":
return target.SFRotation.y;
break;
case "2":
//case "z":
return target.SFRotation.z;
break;
case "3":
case "angle":
return target.SFRotation.angle;
break;
default:
return Reflect.get( target, prop );
}
},
set : function ( target, prop, value )
{
var rot = target.SFRotation;
var propMap = {
0 : "x",
1 : "y",
2 : "z",
x : "x",
y : "y",
z : "z",
angle : "angle"
};
if ( prop in propMap )
{
rot[ propMap[ prop ] ] = value;
target.setValues(
new x3dom.fields.Quaternion.axisAngle(
new x3dom.fields.SFVec3f( rot.x, rot.y, rot.z ), rot.angle
)
);
return true;
}
x3dom.debug.logWarning( " SFRotation: property not available - " + prop );
return false;
}
};
if ( args[ 0 ].constructor == x3dom.fields.SFVec3f )
{
if ( args[ 1 ].constructor == x3dom.fields.SFVec3f )
{
quat = new x3dom.fields.Quaternion.rotateFromTo( args[ 0 ].normalize(), args[ 1 ].normalize() );
}
else
{
quat = new x3dom.fields.Quaternion.axisAngle( args[ 0 ], args[ 1 ] );
}
//save properties
var aa = quat.toAxisAngle();
quat.SFRotation = {
x : aa[ 0 ].x,
y : aa[ 0 ].y,
z : aa[ 0 ].z,
angle : aa[ 1 ]
};
}
else
{
quat = new x3dom.fields.Quaternion.axisAngle( new x3dom.fields.SFVec3f( args[ 0 ], args[ 1 ], args[ 2 ] ), args[ 3 ] );
quat.SFRotation = {
x : args[ 0 ],
y : args[ 1 ],
z : args[ 2 ],
angle : args[ 3 ]
};
}
return new Proxy( quat, handler );
}
} );
/**
* SAI function to return the axis of rotation.
*
* @returns {x3dom.fields.SFVec3f} the axis of rotation
*/
x3dom.fields.Quaternion.prototype.getAxis = function ()
{
if ( "SFRotation" in this )
{
var axis = this.SFRotation;
return new x3dom.fields.SFVec3f( axis.x, axis.y, axis.z );
}
else
{
var aa = this.toAxisAngle();
return aa[ 0 ];
}
};
/**
* SAI function to set the axis of rotation.
* not well tested
* @param {x3dom.fields.SFVec3f} vec - the axis vector to set to.
* @returns {} void
*/
x3dom.fields.Quaternion.prototype.setAxis = function ( vec )
{
var angle;
if ( "SFRotation" in this )
{
angle = this.SFRotation.angle;
this.SFRotation.x = vec.x;
this.SFRotation.y = vec.y;
this.SFRotation.z = vec.z;
}
else
{
angle = this.angle();
}
var q = new x3dom.fields.Quaternion.axisAngle( vec, angle );
this.setValues( q );
};
/**
* inverse: SAI function to return the inverse of this object's rotation.
* see below
* @returns {x3dom.fields.SFRotation} the inverted rotation
*/
/**
* multiply: SAI function to return the object multiplied by the passed value.
* see below
* @returns {x3dom.fields.SFRotation} the multiplied rotation
*/
/**
* SAI function to return the value of vec multiplied by the matrix corresponding to this object's rotation.
*
* @param {x3dom.fields.SFVec3f} vec - the vector to multiply with
* @returns {x3dom.fields.SFVec3f} the axis of rotation
*/
x3dom.fields.Quaternion.prototype.multiVec = function ( vec )
{
var m = x3dom.fields.SFMatrix4f.identity();
m.setRotate( this );
return m.multMatrixVec( vec );
};
/**
* slerp: SAI function to return the value of the spherical linear interpolation between this object's rotation and dest at value 0 ≤ t ≤ 1.
* For t = 0, the value is this object`s rotation. For t = 1, the value is the dest rotation.
* see below, not well tested
* @param {x3dom.fields.SFRotation} dest - the destination rotation
* @param {x3dom.fields.SFFloat} t - the fraction
* @returns {x3dom.fields.SFRotation} the interpolated rotation
*/
/**
* Sets the components of this quaternion from another quaternion, and returns
* this modified quaternion.
*
* @param {x3dom.fields.Quaternion} that - the quaternion to copy from
* @returns {x3dom.fields.Quaternion} this modified quaternion
*/
x3dom.fields.Quaternion.prototype.setValues = function ( that )
{
this.x = that.x;
this.y = that.y;
this.z = that.z;
this.w = that.w;
return this;
};
/**
* Returns a copy of the supplied quaternion.
*
* @param {x3dom.fields.Quaternion} v - the quatenion to copy
* @returns {x3dom.fields.Quaternion} a copy of the supplied quaternion
*/
x3dom.fields.Quaternion.copy = function ( v )
{
return new x3dom.fields.Quaternion( v.x, v.y, v.z, v.w );
};
/**
* Returns the product obtained by multiplying this vector with
* another one.
*
* @param {x3dom.fields.Quaternion} that - the right (multiplicand)
* quaternion
* @returns {x3dom.fields.Quaternion} the product of this quaternion
* and the other one
*/
x3dom.fields.Quaternion.prototype.multiply = function ( that )
{
var product = new x3dom.fields.Quaternion(
this.w * that.x + this.x * that.w + this.y * that.z - this.z * that.y,
this.w * that.y + this.y * that.w + this.z * that.x - this.x * that.z,
this.w * that.z + this.z * that.w + this.x * that.y - this.y * that.x,
this.w * that.w - this.x * that.x - this.y * that.y - this.z * that.z
);
if ( "SFRotation" in this )
{
var aa = product.toAxisAngle();
return new x3dom.fields.SFRotation( aa[ 0 ].x, aa[ 0 ].y, aa[ 0 ].z, aa[ 1 ] );
}
return product;
};
/**
* Sets this quaternion's components from an array of numbers.
*
* @param {Number[]} array - an array of at least four numbers, the first
* four of which will be used to set this
* quaternion's x-, y-, z-, and w-components
* @returns {x3dom.fields.Quaternion} this modified quaternion
*/
x3dom.fields.Quaternion.prototype.fromArray = function ( array )
{
this.x = array[ 0 ];
this.y = array[ 1 ];
this.z = array[ 2 ];
this.w = array[ 3 ];
return this;
};
/**
* Sets this quaternion's components from an array of numbers.
*
* @param {Number[]} array - an array of at least four numbers, the first
* four of which will be used as the x-, y-,
* z-, and w-components in this order.
*/
x3dom.fields.Quaternion.fromArray = function ( array )
{
return new x3dom.fields.Quaternion( array[ 0 ], array[ 1 ], array[ 2 ], array[ 3 ] );
};
/**
* Parses the axis-angle representation of a rotation from a string
* and creates and returns a new quaternion equivalent to it.
*
* @param {String} str - the string to parse the axis-angle data from
* @returns {x3dom.fields.Quaternion} a new quaternion equivalent to the
* axis-angle rotation expressed in
* the parsed input string
*/
x3dom.fields.Quaternion.parseAxisAngle = function ( str )
{
var m = /^\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*$/.exec( str );
if ( m === null ) {return new x3dom.fields.Quaternion();}
return x3dom.fields.Quaternion.axisAngle( new x3dom.fields.SFVec3f( +m[ 1 ], +m[ 2 ], +m[ 3 ] ), +m[ 4 ] );
};
/**
* Creates and returns an axis-angle representation of this quaternion.
*
* @param {x3dom.fields.SFVec3f} axis - the rotation axis
* @param {Number} a - the rotation angle in radians
* @returns {x3dom.fields.Quaternion} a new quaternion equivalent to the
* specified axis-angle representation
*/
x3dom.fields.Quaternion.axisAngle = function ( axis, a )
{
var t = axis.length();
if ( t > x3dom.fields.Eps )
{
var s = Math.sin( a / 2 ) / t;
var c = Math.cos( a / 2 );
return new x3dom.fields.Quaternion( axis.x * s, axis.y * s, axis.z * s, c );
}
else
{
return new x3dom.fields.Quaternion( 0, 0, 0, 1 );
}
};
/**
* Returns a copy of this quaternion.
*
* @returns {x3dom.fields.Quaternion} a copy of this quaternion
*/
x3dom.fields.Quaternion.prototype.copy = function ()
{
return x3dom.fields.Quaternion.copy( this );
};
/**
* Returns a rotation matrix representation of this quaternion.
*
* @returns {x3dom.fields.SFMatrix4} a new rotation matrix representing
* this quaternion's rotation
*/
x3dom.fields.Quaternion.prototype.toMatrix = function ()
{
var xx = this.x * this.x;
var xy = this.x * this.y;
var xz = this.x * this.z;
var yy = this.y * this.y;
var yz = this.y * this.z;
var zz = this.z * this.z;
var wx = this.w * this.x;
var wy = this.w * this.y;
var wz = this.w * this.z;
return new x3dom.fields.SFMatrix4f(
1 - 2 * ( yy + zz ), 2 * ( xy - wz ), 2 * ( xz + wy ), 0,
2 * ( xy + wz ), 1 - 2 * ( xx + zz ), 2 * ( yz - wx ), 0,
2 * ( xz - wy ), 2 * ( yz + wx ), 1 - 2 * ( xx + yy ), 0,
0, 0, 0, 1
);
};
/**
* Returns an axis-angle representation of this quaternion as a
* two-element array containing the rotation axis and angle in radians
* in this order.
*
* @returns {Array} an array of two elements, the first of which is
* the ``SFVec3f'' axis-angle rotation axis,
* the second being the angle in radians.
*/
x3dom.fields.Quaternion.prototype.toAxisAngle = function ()
{
var x = 0,
y = 0,
z = 0;
var s = 0,
a = 0;
if ( this.w > 1 )
{
this.normalize();
}
a = 2 * Math.acos( this.w );
s = Math.sqrt( 1 - this.w * this.w );
if ( s == 0 )
{ //< x3dom.fields.Eps )
x = this.x;
y = this.y;
z = this.z;
}
else
{
x = this.x / s;
y = this.y / s;
z = this.z / s;
}
return [ new x3dom.fields.SFVec3f( x, y, z ), a ];
};
/**
* Returns this quaternion's rotation angle in radians.
*
* @returns {Number} this quaternion's rotation angle in radians
*/
x3dom.fields.Quaternion.prototype.angle = function ()
{
return 2 * Math.acos( this.w );
};
/**
* Sets this quaternion's components from the supplied rotation matrix,
* and returns the quaternion itself.
*
* @param {x3dom.fields.SFMatrix4f} matrix - the rotation matrix whose
* rotation shall be copied
* into this quaternion
* @returns {x3dom.fields.Quaternion} this modified quaternion
*/
x3dom.fields.Quaternion.prototype.setValue = function ( matrix )
{
var tr,
s = 1;
var qt = [ 0, 0, 0 ];
var i = 0,
j = 0,
k = 0;
var nxt = [ 1, 2, 0 ];
tr = matrix._00 + matrix._11 + matrix._22;
if ( tr > 0.0 )
{
s = Math.sqrt( tr + 1.0 );
this.w = s * 0.5;
s = 0.5 / s;
this.x = ( matrix._21 - matrix._12 ) * s;
this.y = ( matrix._02 - matrix._20 ) * s;
this.z = ( matrix._10 - matrix._01 ) * s;
}
else
{
if ( matrix._11 > matrix._00 )
{
i = 1;
}
else
{
i = 0;
}
if ( matrix._22 > matrix.at( i, i ) )
{
i = 2;
}
j = nxt[ i ];
k = nxt[ j ];
s = Math.sqrt( matrix.at( i, i ) - ( matrix.at( j, j ) + matrix.at( k, k ) ) + 1.0 );
qt[ i ] = s * 0.5;
s = 0.5 / s;
this.w = ( matrix.at( k, j ) - matrix.at( j, k ) ) * s;
qt[ j ] = ( matrix.at( j, i ) + matrix.at( i, j ) ) * s;
qt[ k ] = ( matrix.at( k, i ) + matrix.at( i, k ) ) * s;
this.x = qt[ 0 ];
this.y = qt[ 1 ];
this.z = qt[ 2 ];
}
if ( this.w > 1.0 || this.w < -1.0 )
{
var errThreshold = 1 + ( x3dom.fields.Eps * 100 );
if ( this.w > errThreshold || this.w < -errThreshold )
{
// When copying, then everything, incl. the famous OpenSG MatToQuat bug
x3dom.debug.logInfo( "MatToQuat: BUG: |quat[4]| (" + this.w + ") >> 1.0 !" );
}
if ( this.w > 1.0 )
{
this.w = 1.0;
}
else
{
this.w = -1.0;
}
}
};
/**
* Sets this quaternion from the Euler angles representation, and
* returns this quaternion.
*
* @param {Number} alpha - the rotation angle in radians about the
* first axis
* @param {Number} beta - the rotation angle in radians about the
* second axis
* @param {Number} gamma - the rotation angle in radians about the
* third axis
* @returns {x3dom.fields.Quaternion} the modified quaternion
*/
x3dom.fields.Quaternion.prototype.setFromEuler = function ( alpha, beta, gamma )
{
var sx = Math.sin( alpha * 0.5 );
var cx = Math.cos( alpha * 0.5 );
var sy = Math.sin( beta * 0.5 );
var cy = Math.cos( beta * 0.5 );
var sz = Math.sin( gamma * 0.5 );
var cz = Math.cos( gamma * 0.5 );
this.x = ( sx * cy * cz ) - ( cx * sy * sz );
this.y = ( cx * sy * cz ) + ( sx * cy * sz );
this.z = ( cx * cy * sz ) - ( sx * sy * cz );
this.w = ( cx * cy * cz ) + ( sx * sy * sz );
return this;
};
/**
* Returns the dot product of this quaternion and another one.
*
* @param {x3dom.fields.Quaternion} that - the right quaternion
* @returns {Number} the production of this quaternion and the other one
*/
x3dom.fields.Quaternion.prototype.dot = function ( that )
{
return this.x * that.x + this.y * that.y + this.z * that.z + this.w * that.w;
};
/**
* Returns a new quaternion obtained by adding to this quaternion
* the components of another one.
*
* @param {x3dom.fields.Quaternion} that - the quaternion to add to this
* one
* @returns {x3dom.fields.Quaternion} a new quaternion representing the
* sum of this and the other quaternion
*/
x3dom.fields.Quaternion.prototype.add = function ( that )
{
return new x3dom.fields.Quaternion( this.x + that.x, this.y + that.y, this.z + that.z, this.w + that.w );
};
/**
* Returns a new quaternion as the difference between this quaternion
* and another one subtracted from it.
*
* @param {x3dom.fields.Quaternion} that - the quaternion to deduct
* from this one
* @returns {x3dom.fields.Quaternion} a new quaternion holding the
* difference
*/
x3dom.fields.Quaternion.prototype.subtract = function ( that )
{
return new x3dom.fields.Quaternion( this.x - that.x, this.y - that.y, this.z - that.z, this.w - that.w );
};
/**
* Sets this quaternion's components from another quaternion, and
* returns this quaternion.
*
* @param {x3dom.fields.Quaternion} that - the quaternion to copy from
* @returns {x3dom.fields.Quaternion} this modified quaternion
*/
x3dom.fields.Quaternion.prototype.setValues = function ( that )
{
this.x = that.x;
this.y = that.y;
this.z = that.z;
this.w = that.w;
return this;
};
/**
* Checks whether this quaternion equals another one.
*
* @param {x3dom.fields.Quaternion} that - the quaternion to juxtapose
* with this one
* @param {Number} eps - the tolerance of deviation
* within which not exactly equal
* components are still considered
* equal
* @returns {Boolean} ``true'' if both quaternions are equal
* or approximately equal, ``false'' otherwise
*/
x3dom.fields.Quaternion.prototype.equals = function ( that, eps )
{
return ( this.dot( that ) >= 1.0 - eps );
};
/**
* Returns a scaled version of this quaternion.
*
* @param {Number} s - the scalar scale factor
* @returns {x3dom.fields.Quaternion} a scaled version of this quaternion
*/
x3dom.fields.Quaternion.prototype.multScalar = function ( s )
{
return new x3dom.fields.Quaternion( this.x * s, this.y * s, this.z * s, this.w * s );
};
/**
* Normalizes and returns this quaternion.
*
* @returns {x3dom.fields.Quaternion} this normalized quaternion
*/
x3dom.fields.Quaternion.prototype.normalize = function ()
{
var d2 = this.dot( this );
var id = 1.0;
if ( d2 )
{
id = 1.0 / Math.sqrt( d2 );
}
this.x *= id;
this.y *= id;
this.z *= id;
this.w *= id;
return this;
};
/**
* Normalizes a quaternion and returns it.
*
* @param {x3dom.fields.Quaternion} that - the quaternion to be
* normalized
* @returns {x3dom.fields.Quaternion} the normalized input quaternion
*/
x3dom.fields.Quaternion.normalize = function ( that )
{
var d2 = that.dot( that );
var id = 1.0;
if ( d2 )
{
id = 1.0 / Math.sqrt( d2 );
}
that.x *= id;
that.y *= id;
that.z *= id;
that.w *= id;
return that;
};
/**
* Returns a negated version of this quaternion.
*
* The negation of a quaternion negates all of its components.
*
* @returns {x3dom.fields.Quaternion} the negated version of this
* quaternion
*/
x3dom.fields.Quaternion.prototype.negate = function ()
{
return new x3dom.fields.Quaternion( -this.x, -this.y, -this.z, -this.w );
};
/**
* Returns an inverted version of this quaternion.
*
* The conjugate or inverse quaternion negates the x-, y-, and
* z-components, while retaining the w-coordinate's signum.
*
* @returns {x3dom.fields.Quaternion} the inverted version of this
* quaternion
*/
x3dom.fields.Quaternion.prototype.inverse = function ()
{
var inverse = new x3dom.fields.Quaternion( -this.x, -this.y, -this.z, this.w );
if ( "SFRotation" in this )
{
var aa = inverse.toAxisAngle();
return new x3dom.fields.SFRotation( aa[ 0 ].x, aa[ 0 ].y, aa[ 0 ].z, aa[ 1 ] );
}
return inverse;
};
/**
* Returns the result of performing a spherical linear interpolation,
* or slerp, between this quaternion and another one as defined
* by the supplied ratio.
*
* @param {x3dom.fields.Quaternion} that - the opposite end of the
* interpolation
* @param {Number} t - the ratio of the interpolation
* between the two quaternions,
* usually as a floating-point
* value in the [0.0, 1.0]
* @returns {x3dom.fields.Quaternion} a new quaternion which represents
* the interpolated value between
* this and the opposite quaternion
* at the given ratio
*/
x3dom.fields.Quaternion.prototype.slerp = function ( that, t )
{
// calculate the cosine
var cosom = this.dot( that );
var rot1;
// adjust signs if necessary
if ( cosom < 0.0 )
{
cosom = -cosom;
rot1 = that.negate();
}
else
{
rot1 = new x3dom.fields.Quaternion( that.x, that.y, that.z, that.w );
}
// calculate interpolating coeffs
var scalerot0;
var scalerot1;
if ( ( 1.0 - cosom ) > 0.00001 )
{
// standard case
var omega = Math.acos( cosom );
var sinom = Math.sin( omega );
scalerot0 = Math.sin( ( 1.0 - t ) * omega ) / sinom;
scalerot1 = Math.sin( t * omega ) / sinom;
}
else
{
// rot0 and rot1 very close - just do linear interp.
scalerot0 = 1.0 - t;
scalerot1 = t;
}
// build the new quaternion
var result = this.multScalar( scalerot0 ).add( rot1.multScalar( scalerot1 ) );
if ( "SFRotation" in this )
{
var aa = result.toAxisAngle();
return new x3dom.fields.SFRotation( aa[ 0 ].x, aa[ 0 ].y, aa[ 0 ].z, aa[ 1 ] );
}
return result;
};
/**
* Computes and returns a quaternion representing the rotation necessary
* to reach align the first vector with the second one.
*
* @param {x3dom.fields.SFVec3f} fromVec - the start vector which shall
* be construed as being
* intended to be aligned with
* the ``toVec''
* @param {x3dom.fields.SFVec3f} toVec - the vector whose orientation
* shall be reached by the
* ``fromVec''
* @returns {x3dom.fields.Quaternion} the quaternion which represents
* the rotation necessary to align
* the ``fromVec'' with the
* ``toVec''
*/
x3dom.fields.Quaternion.rotateFromTo = function ( fromVec, toVec )
{
var from = fromVec.normalize();
var to = toVec.normalize();
var cost = from.dot( to );
// check for degeneracies
if ( cost > 0.99999 )
{
// vectors are parallel
return new x3dom.fields.Quaternion( 0, 0, 0, 1 );
}
else if ( cost < -0.99999 )
{
// vectors are opposite
// find an axis to rotate around, which should be
// perpendicular to the original axis
// Try cross product with (1,0,0) first, if that's one of our
// original vectors then try (0,1,0).
var cAxis = new x3dom.fields.SFVec3f( 1, 0, 0 );
var tmp = from.cross( cAxis );
if ( tmp.length() < 0.00001 )
{
cAxis.x = 0;
cAxis.y = 1;
cAxis.z = 0;
tmp = from.cross( cAxis );
}
tmp = tmp.normalize();
return x3dom.fields.Quaternion.axisAngle( tmp, Math.PI );
}
var axis = fromVec.cross( toVec );
axis = axis.normalize();
// use half-angle formulae
// sin^2 t = ( 1 - cos (2t) ) / 2
var s = Math.sqrt( 0.5 * ( 1.0 - cost ) );
axis = axis.multiply( s );
// scale the axis by the sine of half the rotation angle to get
// the normalized quaternion
// cos^2 t = ( 1 + cos (2t) ) / 2
// w part is cosine of half the rotation angle
s = Math.sqrt( 0.5 * ( 1.0 + cost ) );
return new x3dom.fields.Quaternion( axis.x, axis.y, axis.z, s );
};
/**
* Returns a four-element array of this quaternion's rotation in
* an axis-angle representation suitable for communication with OpenGL.
*
* @returns {Number[]} an array of four numbers holding the rotation
* axis' x-, y-, and z-coordinates, as well as the
* angle in radians
*/
x3dom.fields.Quaternion.prototype.toGL = function ()
{
var val = this.toAxisAngle();
return [ val[ 0 ].x, val[ 0 ].y, val[ 0 ].z, val[ 1 ] ];
};
/**
* Returns a string representation of this quaternion.
*
* @returns {String} a string representation of this quaternion
*/
x3dom.fields.Quaternion.prototype.toString = function ()
{
if ( "SFRotation" in this )
{
return this.SFRotation.x + " " + this.SFRotation.y + " " + this.SFRotation.z + " " + this.SFRotation.angle;
}
return this.x + " " + this.y + " " + this.z + ", " + this.w;
};
/**
* Parses the axis-angle representation of a rotation from a string
* and sets this quaternion's rotation from it, finally returning this
* quaternion itself.
*
* @param {String} str - the string to parse the axis-angle data from
* @returns {x3dom.fields.Quaternion} this modified quaternion
*/
x3dom.fields.Quaternion.prototype.setValueByStr = function ( str )
{
var m = /^\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*$/.exec( str );
m = m || [ 0, 1, 0, 0, 0 ];
var quat = x3dom.fields.Quaternion.axisAngle( new x3dom.fields.SFVec3f( +m[ 1 ], +m[ 2 ], +m[ 3 ] ), +m[ 4 ] );
this.x = quat.x;
this.y = quat.y;
this.z = quat.z;
this.w = quat.w;
return this;
};
/**
* Parses a string and creates and returns a color based upon it.
*
* The input string might represent the color information in a variety
* of formats, encompassing a CSS-conformant color name, an invocation
* of ``rgb(red, green, blue)'', an invocation of
* ``rgba(red, green, blue, alpha)'', or a hexadecimal representation of
* the kind ``#RRGGBBAA'', ``#RRGGBB'', ``#RGBA'', or ``#RGB''.
*
* The thus generated object exhibits the following structure:
* {
* r : <red>,
* g : <green>,
* b : <blue>,
* a : <alpha>
* }
* All components are normalized in the floating-point range [0.0, 1.0].
* If none of the above formats can be detected, all components are
* instead set to zero.
*
* @param {String} str - the string to parse the color information from
* @returns {Object} an object containing the four color components,
* each such represented by a property ``r'', ``g'',
* ``b'', ``a'', whose value is normalized in the range
* [0.0, 1.0]; failure to match a color format yields
* zero for all components
*/
function _colorParse ( str )
{
var red = 0.0,
green = 0.0,
blue = 0.0;
var alpha = 1.0;
// Definition of CSS color names
var colorNames = {
aliceblue : "#f0f8ff",
antiquewhite : "#faebd7",
aqua : "#00ffff",
aquamarine : "#7fffd4",
azure : "#f0ffff",
beige : "#f5f5dc",
bisque : "#ffe4c4",
black : "#000000",
blanchedalmond : "#ffebcd",
blue : "#0000ff",
blueviolet : "#8a2be2",
brown : "#a52a2a",
burlywood : "#deb887",
cadetblue : "#5f9ea0",
chartreuse : "#7fff00",
chocolate : "#d2691e",
coral : "#ff7f50",
cornflowerblue : "#6495ed",
cornsilk : "#fff8dc",
crimson : "#dc143c",
cyan : "#00ffff",
darkblue : "#00008b",
darkcyan : "#008b8b",
darkgoldenrod : "#b8860b",
darkgray : "#a9a9a9",
darkgreen : "#006400",
darkkhaki : "#bdb76b",
darkmagenta : "#8b008b",
darkolivegreen : "#556b2f",
darkorange : "#ff8c00",
darkorchid : "#9932cc",
darkred : "#8b0000",
darksalmon : "#e9967a",
darkseagreen : "#8fbc8f",
darkslateblue : "#483d8b",
darkslategray : "#2f4f4f",
darkturquoise : "#00ced1",
darkviolet : "#9400d3",
deeppink : "#ff1493",
deepskyblue : "#00bfff",
dimgray : "#696969",
dodgerblue : "#1e90ff",
feldspar : "#d19275",
firebrick : "#b22222",
floralwhite : "#fffaf0",
forestgreen : "#228b22",
fuchsia : "#ff00ff",
gainsboro : "#dcdcdc",
ghostwhite : "#f8f8ff",
gold : "#ffd700",
goldenrod : "#daa520",
gray : "#808080",
green : "#008000",
greenyellow : "#adff2f",
honeydew : "#f0fff0",
hotpink : "#ff69b4",
indianred : "#cd5c5c",
indigo : "#4b0082",
ivory : "#fffff0",
khaki : "#f0e68c",
lavender : "#e6e6fa",
lavenderblush : "#fff0f5",
lawngreen : "#7cfc00",
lemonchiffon : "#fffacd",
lightblue : "#add8e6",
lightcoral : "#f08080",
lightcyan : "#e0ffff",
lightgoldenrodyellow : "#fafad2",
lightgrey : "#d3d3d3",
lightgreen : "#90ee90",
lightpink : "#ffb6c1",
lightsalmon : "#ffa07a",
lightseagreen : "#20b2aa",
lightskyblue : "#87cefa",
lightslateblue : "#8470ff",
lightslategray : "#778899",
lightsteelblue : "#b0c4de",
lightyellow : "#ffffe0",
lime : "#00ff00",
limegreen : "#32cd32",
linen : "#faf0e6",
magenta : "#ff00ff",
maroon : "#800000",
mediumaquamarine : "#66cdaa",
mediumblue : "#0000cd",
mediumorchid : "#ba55d3",
mediumpurple : "#9370d8",
mediumseagreen : "#3cb371",
mediumslateblue : "#7b68ee",
mediumspringgreen : "#00fa9a",
mediumturquoise : "#48d1cc",
mediumvioletred : "#c71585",
midnightblue : "#191970",
mintcream : "#f5fffa",
mistyrose : "#ffe4e1",
moccasin : "#ffe4b5",
navajowhite : "#ffdead",
navy : "#000080",
oldlace : "#fdf5e6",
olive : "#808000",
olivedrab : "#6b8e23",
orange : "#ffa500",
orangered : "#ff4500",
orchid : "#da70d6",
palegoldenrod : "#eee8aa",
palegreen : "#98fb98",
paleturquoise : "#afeeee",
palevioletred : "#d87093",
papayawhip : "#ffefd5",
peachpuff : "#ffdab9",
peru : "#cd853f",
pink : "#ffc0cb",
plum : "#dda0dd",
powderblue : "#b0e0e6",
purple : "#800080",
red : "#ff0000",
rosybrown : "#bc8f8f",
royalblue : "#4169e1",
saddlebrown : "#8b4513",
salmon : "#fa8072",
sandybrown : "#f4a460",
seagreen : "#2e8b57",
seashell : "#fff5ee",
sienna : "#a0522d",
silver : "#c0c0c0",
skyblue : "#87ceeb",
slateblue : "#6a5acd",
slategray : "#708090",
snow : "#fffafa",
springgreen : "#00ff7f",
steelblue : "#4682b4",
tan : "#d2b48c",
teal : "#008080",
thistle : "#d8bfd8",
tomato : "#ff6347",
turquoise : "#40e0d0",
violet : "#ee82ee",
violetred : "#d02090",
wheat : "#f5deb3",
white : "#ffffff",
whitesmoke : "#f5f5f5",
yellow : "#ffff00",
yellowgreen : "#9acd32"
};
// Matches CSS rgb() function
var rgbMatch = /^rgb\((\d{1,3}),\s{0,1}(\d{1,3}),\s{0,1}(\d{1,3})\)$/.exec( str );
if ( rgbMatch !== null )
{
red = rgbMatch[ 1 ] / 255.0;
green = rgbMatch[ 2 ] / 255.0;
blue = rgbMatch[ 3 ] / 255.0;
}
// Matches CSS rgba() function
var rgbaMatch = /^rgba\((\d{1,3}),\s{0,1}(\d{1,3}),\s{0,1}(\d{1,3}),(0+\.?\d*|1\.?0*)\)$/.exec( str );
if ( rgbaMatch !== null )
{
red = rgbaMatch[ 1 ] / 255.0;
green = rgbaMatch[ 2 ] / 255.0;
blue = rgbaMatch[ 3 ] / 255.0;
alpha = +rgbaMatch[ 4 ];
}
// Matches CSS color name
if ( colorNames[ str ] )
{
str = colorNames[ str ];
}
// Hexadecimal color codes
if ( str.substr && str.substr( 0, 1 ) === "#" )
{
var hex = str.substr( 1 );
var len = hex.length;
if ( len === 8 )
{
red = parseInt( "0x" + hex.substr( 0, 2 ), 16 ) / 255.0;
green = parseInt( "0x" + hex.substr( 2, 2 ), 16 ) / 255.0;
blue = parseInt( "0x" + hex.substr( 4, 2 ), 16 ) / 255.0;
alpha = parseInt( "0x" + hex.substr( 6, 2 ), 16 ) / 255.0;
}
else if ( len === 6 )
{
red = parseInt( "0x" + hex.substr( 0, 2 ), 16 ) / 255.0;
green = parseInt( "0x" + hex.substr( 2, 2 ), 16 ) / 255.0;
blue = parseInt( "0x" + hex.substr( 4, 2 ), 16 ) / 255.0;
}
else if ( len === 4 )
{
red = parseInt( "0x" + hex.substr( 0, 1 ), 16 ) / 15.0;
green = parseInt( "0x" + hex.substr( 1, 1 ), 16 ) / 15.0;
blue = parseInt( "0x" + hex.substr( 2, 1 ), 16 ) / 15.0;
alpha = parseInt( "0x" + hex.substr( 3, 1 ), 16 ) / 15.0;
}
else if ( len === 3 )
{
red = parseInt( "0x" + hex.substr( 0, 1 ), 16 ) / 15.0;
green = parseInt( "0x" + hex.substr( 1, 1 ), 16 ) / 15.0;
blue = parseInt( "0x" + hex.substr( 2, 1 ), 16 ) / 15.0;
}
}
return { r: red, g: green, b: blue, a: alpha };
}
/**
* SFColor constructor.
*
* This class represents a color as a triple of floating-point ratios,
* usually confined to the range [0.0, 1.0], but not mandatorily
* restricted in this sense. The three color components or channels
* encompass red, green, and blue, that is, the RGB color model.
*
* @class Represents a SFColor
*/
x3dom.fields.SFColor = function ( r, g, b )
{
if ( arguments.length === 0 )
{
this.r = 0;
this.g = 0;
this.b = 0;
}
else
{
this.r = r;
this.g = g;
this.b = b;
}
};
/**
* Parses a string and returns and creates a color from it.
*
* The input string must convey four numbers, which will be construed
* as the red, green, blue, and alpha channel in this order. If this
* format cannot be detected, the more potent rules of the method
* ``x3dom.fields.SFColor.colorParse'' will be applied, which see.
*
* @param {String} str - the string to parse the color information from
* @returns {x3dom.fields.SFColor} the color parsed from the string,
* or a color with all components
* zero-valued if none format could
* be matched
*/
x3dom.fields.SFColor.parse = function ( str )
{
try
{
var m = /^\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*$/.exec( str );
return new x3dom.fields.SFColor( +m[ 1 ], +m[ 2 ], +m[ 3 ] );
}
catch ( e )
{
return x3dom.fields.SFColor.colorParse( str );
}
};
/**
* Returns a copy of the specified color.
*
* @param {x3dom.fields.SFColor} that - the color to copy
* @returns {x3dom.fields.SFColor} a copy of the input color
*/
x3dom.fields.SFColor.copy = function ( that )
{
return new x3dom.fields.SFColor( that.r, that.g, that.b );
};
/**
* Returns a copy of this color.
*
* @returns {x3dom.fields.SFColor} a copy of this color
*/
x3dom.fields.SFColor.prototype.copy = function ()
{
return x3dom.fields.SFColor.copy( this );
};
/**
* Sets the components of this color from the supplied hue, saturation
* and value as according to the HSV color model.
*
* @param {Number} h - the hue in degrees
* @param {Number} s - the saturation as a floating-point ratio in
* the range [0.0, 1.0]
* @param {Number} v - the value as a floating-point ratio in
* the range [0.0, 1.0]
* @returns {x3dom.fields.SFColor} this color itself
*/
x3dom.fields.SFColor.prototype.setHSV = function ( h, s, v )
{
var hi = 0;
var f = 0;
var p = 0;
var q = 0;
var t = 0;
var r = 0;
var g = 0;
var b = 0;
hi = Math.floor( h / 60.0 );
f = ( h / 60.0 ) - hi;
p = v * ( 1.0 - s );
q = v * ( 1.0 - ( s * f ) );
t = v * ( 1.0 - ( s * ( 1.0 - f ) ) );
switch ( hi )
{
case 0 :
case 6 :
{
r = v;
g = t;
b = p;
break;
}
case 1 :
{
r = q;
g = v;
b = p;
break;
}
case 2 :
{
r = p;
g = v;
b = t;
break;
}
case 3 :
{
r = p;
g = q;
b = v;
break;
}
case 4 :
{
r = t;
g = p;
b = v;
break;
}
case 5 :
{
r = v;
g = p;
b = q;
break;
}
default :
{
x3dom.debug.logWarning( "Using black for invalid case in setHSV: " + hi );
break;
}
};
this.r = r;
this.g = g;
this.b = b;
return this;
};
/**
* Returns the HSV color components corresponding to this color as an
* array of three numbers.
*
* In accordance with the widespread wont, the hue is delivered as an
* angle in degrees in the range [0.0, 360.0]; the saturation and value
* both are ratios in the range [0.0, 1.0].
*
* @see{@link https://www.rapidtables.com/convert/color/rgb-to-hsv.html}}
* @see{@link https://en.wikipedia.org/wiki/HSL_and_HSV}}
* @returns {Number[]} the three HSV color components corresponding to
* this color as elements of an array in the order
* hue, saturation, and value
*/
x3dom.fields.SFColor.prototype.getHSV = function ()
{
var hue = 0; // H
var saturation = 0; // S
var val = 0; // V
var maxComponent = {}; // C_max
var componentRange = 0; // MAX - MIN
var minComponentValue = this.r;
maxComponent.name = "red";
maxComponent.value = this.r;
if ( this.g < minComponentValue )
{
minComponentValue = this.g;
}
if ( this.b < minComponentValue )
{
minComponentValue = this.b;
}
if ( this.g > maxComponent.value )
{
maxComponent.name = "green";
maxComponent.value = this.g;
}
if ( this.b > maxComponent.value )
{
maxComponent.name = "blue";
maxComponent.value = this.b;
}
componentRange = maxComponent.value - minComponentValue;
if ( componentRange == 0.0 )
{
hue = 0;
}
else if ( maxComponent.name == "red" )
{
hue = 60.0 * ( ( ( this.g - this.b ) / componentRange ) % 6 );
}
else if ( maxComponent.name == "green" )
{
hue = 60.0 * ( ( ( this.b - this.r ) / componentRange ) + 2.0 );
}
else if ( maxComponent.name == "blue" )
{
hue = 60.0 * ( ( ( this.r - this.g ) / componentRange ) + 4.0 );
}
else
{
throw ( "Unknown maximum component: " + maxComponent.name );
}
if ( hue < 0 )
{
hue = hue + 360;
}
if ( maxComponent.value == 0 )
{
saturation = 0;
}
else
{
saturation = componentRange / maxComponent.value;
}
val = maxComponent.value;
return [ hue, saturation, val ];
};
/**
* Sets this color's components to that of the supplied color and
* returns this modified color.
*
* @param {x3dom.fields.SFColor} color - the color to copy from
* @returns {x3dom.fields.SFColor} this modified color
*/
x3dom.fields.SFColor.prototype.setValues = function ( color )
{
this.r = color.r;
this.g = color.g;
this.b = color.b;
return this;
};
/**
* Checks whether this color equals another one in circumference of
* the specified tolerance.
*
* @param {x3dom.fields.SFColor} that - a copy of this color
* @param {Number} eps - the tolerance of deviation
* between the two tested colors'
* components within which they
* might still be considered as
* equal
* @returns {Boolean} ``true'' if the two colors are equal,
* ``false'' otherwise
*/
x3dom.fields.SFColor.prototype.equals = function ( that, eps )
{
return Math.abs( this.r - that.r ) < eps &&
Math.abs( this.g - that.g ) < eps &&
Math.abs( this.b - that.b ) < eps;
};
/**
* Returns a new RGB color as the sum of this color and another one.
*
* @param {x3dom.fields.SFColor|x3dom.fields.SFColorRGBA} that -
* the color to add to this one
* @returns {x3dom.fields.SFColor} a new color with its components being
* that of this one augmented by the
* supplied second color
*/
x3dom.fields.SFColor.prototype.add = function ( that )
{
return new x3dom.fields.SFColor( this.r + that.r, this.g + that.g, this.b + that.b );
};
/**
* Returns a new RGB color as the difference between this color and
* another one.
*
* @param {x3dom.fields.SFColor|x3dom.fields.SFColorRGBA} that -
* the color to subtract from this one
* @returns {x3dom.fields.SFColor} a new color with its components
* being that of this one reduced by the
* supplied second color
*/
x3dom.fields.SFColor.prototype.subtract = function ( that )
{
return new x3dom.fields.SFColor( this.r - that.r, this.g - that.g, this.b - that.b );
};
/**
* Returns a version of this color whose components have been scaled by
* the supplied scalar factor.
*
* @param {Number} n - the scalar factor to scale each component by
* @returns {x3dom.fields.SFColor} a new color based upon this one
* with each component scaled by the
* supplied factor
*/
x3dom.fields.SFColor.prototype.multiply = function ( n )
{
return new x3dom.fields.SFColor( this.r * n, this.g * n, this.b * n );
};
/**
* Returns a single integer-encoded representation of this RGBA color.
*
* The generated encoding encompasses at most 24 bits, with each eight
* consecutive bits reserved for one of the color components. The bits
* 0 to 7 encode the blue channel; the bits 8 to 15 store the green
* channel; the bits 16 to 23 hold the red channel. The format is thus
* visually: RRRRRRRRGGGGGGGGBBBBBBBB.
*
* @returns {Number} a 32-bit integer representation of this color's
* components, encoded in its lower 24 bits.
*/
x3dom.fields.SFColor.prototype.toUint = function ()
{
return ( ( Math.round( this.r * 255 ) << 16 ) |
( Math.round( this.g * 255 ) << 8 ) |
Math.round( this.b * 255 ) ) >>> 0;
};
/**
* Sets this color's components from a single integer-encoded value
* holding all channel data in its bits, and returns this color.
*
* The supplied integer number is considered regarding its first 24
* bits, each consecutive eight of which represent the integer value
* of one component in the range of [0, 255]. The keys are as follows:
* - Bits 0 to 7 encode the blue channel .
* - Bits 8 to 15 encode the green channel.
* - Bits 16 to 23 encode the red channel.
* The format is thus visually:
* RRRRRRRRGGGGGGGGBBBBBBBB
*
* @param {Number} rgbInteger - a 32-bit integer representation of this
* color's new components
* @returns {x3dom.fields.SFColor} this modified color
*/
x3dom.fields.SFColor.prototype.setFromUint = function ( rgbInteger )
{
this.r = ( ( ( rgbInteger >> 16 ) & 255 ) / 255 );
this.g = ( ( ( rgbInteger >> 8 ) & 255 ) / 255 );
this.b = ( ( ( rgbInteger >> 0 ) & 255 ) / 255 );
return this;
};
/**
* Returns the components of this color as an OpenGL-conformant array
* of three numbers.
*
* @returns {Number[]} an array of three numbers which represent this
* color's components in the order red, green, and
* blue
*/
x3dom.fields.SFColor.prototype.toGL = function ()
{
return [ this.r, this.g, this.b ];
};
/**
* Returns a string representation of this color.
*
* @returns {String} a string representation of this color
*/
x3dom.fields.SFColor.prototype.toString = function ()
{
return this.r + " " + this.g + " " + this.b;
};
/**
* Parses a string, sets this color's components from the parsed data,
* and returns this color.
*
* The underlying examination process is a two-step approach, the first
* of which endeavors to parse three numbers, separated by whitespaces
* and/or comma, to retrieve the color components from these. If this
* fails, a second step is undertaken, in which the input string is
* matched against the more versatile CSS rules, for which see
* {@link x3dom.fields.SFColor.colorParse}. Failure to obtain a
* result in this case leads to this color being left unmodified.
*
* @param {String} str - the string to parse, either as three numbers,
* or as a CSS color specification
* @returns {x3dom.fields.SFColor} this potentially modified color
*/
x3dom.fields.SFColor.prototype.setValueByStr = function ( str )
{
try
{
var m = /^\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*$/.exec( str );
this.r = +m[ 1 ];
this.g = +m[ 2 ];
this.b = +m[ 3 ];
}
catch ( e )
{
var c = x3dom.fields.SFColor.colorParse( str );
this.r = c.r;
this.g = c.g;
this.b = c.b;
}
return this;
};
/**
* Parses a string and creates and returns a color based upon it.
*
* The input string might represent the color information in a variety
* of formats, encompassing a CSS-conformant color name, an invocation
* of ``rgb(red, green, blue)'', an invocation of
* ``rgba(red, green, blue, alpha)'', or a hexadecimal representation of
* the kind ``#RRGGBBAA'', ``#RRGGBB'', ``#RGBA'', or ``#RGB''.
*
* @param {String} color - the string to parse the color information
* from
* @returns {x3dom.fields.SFColor} the color parsed from the string,
* or a color with all components
* zero-valued if none format could
* be matched
*/
x3dom.fields.SFColor.colorParse = function ( color )
{
var rgb = _colorParse( color );
return new x3dom.fields.SFColor( rgb.r, rgb.g, rgb.b );
};
/**
* SFColorRGBA constructor.
*
* This class represents a color as a quadruple of floating-point ratios,
* usually confined to the range [0.0, 1.0], but not mandatorily
* restricted in this sense. The four color components or channels
* encompass red, green, blue, and alpha, that is, the RGBA color model.
* The alpha component defines the opacity of a color, which is the
* opposite of its transparency.
*
* @class Represents a SFColorRGBA
*/
x3dom.fields.SFColorRGBA = function ( r, g, b, a )
{
if ( arguments.length === 0 )
{
this.r = 0;
this.g = 0;
this.b = 0;
this.a = 1;
}
else
{
this.r = r;
this.g = g;
this.b = b;
this.a = a;
}
};
/**
* Parses a string and returns and creates a color from it.
*
* The input string must convey four numbers, which will be construed
* as the red, green, blue, and alpha channel in this order. If this
* format cannot be detected, the more potent rules of the method
* ``x3dom.fields.SFColorRGBA.colorParse'' will be applied, which see.
*
* @param {String} str - the string to parse the color information from
* @returns {x3dom.fields.SFColorRGBA} the color parsed from the string,
* or a color with all components
* zero-valued if none format could
* be matched
*/
x3dom.fields.SFColorRGBA.parse = function ( str )
{
try
{
var m = /^([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)$/.exec( str );
return new x3dom.fields.SFColorRGBA( +m[ 1 ], +m[ 2 ], +m[ 3 ], +m[ 4 ] );
}
catch ( e )
{
return x3dom.fields.SFColorRGBA.colorParse( str );
}
};
/**
* Returns a copy of the supplied color.
*
* @param {x3dom.fields.SFColorRGBA} that - the color to copy
* @returns {x3dom.fields.SFColorRGBA} a copy of the input color
*/
x3dom.fields.SFColorRGBA.copy = function ( that )
{
return new x3dom.fields.SFColorRGBA( that.r, that.g, that.b, that.a );
};
/**
* Returns a copy of this color.
*
* @returns {x3dom.fields.SFColorRGBA} a copy of this color
*/
x3dom.fields.SFColorRGBA.prototype.copy = function ()
{
return x3dom.fields.SFColorRGBA.copy( this );
};
/**
* Sets the components of this color from the supplied hue, saturation
* and value as according to the HSV color model.
*
* @param {Number} h - the hue in degrees
* @param {Number} s - the saturation as a floating-point ratio in
* the range [0.0, 1.0]
* @param {Number} v - the value as a floating-point ratio in
* the range [0.0, 1.0]
* @returns {x3dom.fields.SFColorRGBA} this color itself
*/
x3dom.fields.SFColorRGBA.prototype.setHSV = x3dom.fields.SFColor.prototype.setHSV;
/**
* Returns the HSV color components corresponding to this color as an
* array of three numbers.
*
* In accordance with the widespread wont, the hue is delivered as an
* angle in degrees in the range [0.0, 360.0]; the saturation and value
* both are ratios in the range [0.0, 1.0].
*
* @see{@link https://www.rapidtables.com/convert/color/rgb-to-hsv.html}}
* @see{@link https://en.wikipedia.org/wiki/HSL_and_HSV}}
* @returns {Number[]} the three HSV color components corresponding to
* this color as elements of an array in the order
* hue, saturation, and value
*/
x3dom.fields.SFColorRGBA.prototype.getHSV = x3dom.fields.SFColor.prototype.getHSV;
/**
* Sets this color's components to that of the supplied color and
* returns this modified color.
*
* @param {x3dom.fields.SFColorRGBA} color - the color to copy from
* @returns {x3dom.fields.SFColorRGBA} this modified color
*/
x3dom.fields.SFColorRGBA.prototype.setValues = function ( color )
{
this.r = color.r;
this.g = color.g;
this.b = color.b;
this.a = color.a;
return this;
};
/**
* Checks whether this color equals another one in circumference of
* the specified tolerance.
*
* @param {x3dom.fields.SFColorRGBA} that - a copy of this color
* @param {Number} eps - the tolerance of deviation
* between the two tested colors'
* components within which they
* might still be considered as
* equal
* @returns {Boolean} ``true'' if the two colors are equal,
* ``false'' otherwise
*/
x3dom.fields.SFColorRGBA.prototype.equals = function ( that, eps )
{
return Math.abs( this.r - that.r ) < eps &&
Math.abs( this.g - that.g ) < eps &&
Math.abs( this.b - that.b ) < eps &&
Math.abs( this.a - that.a ) < eps;
};
/**
* Returns the components of this color as an OpenGL-conformant array
* of four numbers.
*
* @returns {Number[]} an array of four numbers which represent this
* color's components in the order red, green, blue,
* and alpha
*/
x3dom.fields.SFColorRGBA.prototype.toGL = function ()
{
return [ this.r, this.g, this.b, this.a ];
};
/**
* Returns a string representation of this color.
*
* @returns {String} a string representation of this color.
*/
x3dom.fields.SFColorRGBA.prototype.toString = function ()
{
return this.r + " " + this.g + " " + this.b + " " + this.a;
};
/**
* Parses a string, sets this color's components from the parsed data,
* and returns this color.
*
* The underlying examination process is a two-step approach, the first
* of which endeavors to parse four numbers, separated by whitespaces
* and/or comma, to retrieve the color components from these. If this
* fails, a second step is undertaken, in which the input string is
* matched against the more versatile CSS rules, for which see
* {@link x3dom.fields.SFColorRGBA.colorParse}. Failure to obtain a
* result in this case leads to this color being left unmodified.
*
* @param {String} str - the string to parse, either as four numbers,
* or as a CSS color specification
* @returns {x3dom.fields.SFColorRGBA} this potentially modified color
*/
x3dom.fields.SFColorRGBA.prototype.setValueByStr = function ( str )
{
try
{
var m = /^([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)$/.exec( str );
this.r = +m[ 1 ];
this.g = +m[ 2 ];
this.b = +m[ 3 ];
this.a = +m[ 4 ];
}
catch ( e )
{
var c = x3dom.fields.SFColorRGBA.colorParse( str );
this.r = c.r;
this.g = c.g;
this.b = c.b;
this.a = c.a;
}
return this;
};
/**
* Returns a single integer-encoded representation of this RGBA color.
*
* The generated encoding encompasses at most 32 bits, with each eight
* consecutive bits reserved for one of the color components. The bits
* 0 to 7 encode the alpha channel; the bits 8 to 15 store the blue
* channel; the bits 16 to 23 hold the green channel; the bits 24 to 31
* represent the red channel. The format is thus visually:
* RRRRRRRRGGGGGGGGBBBBBBBBAAAAAAAA.
*
* @returns {Number} a 32-bit integer representation of this color's
* components
*/
x3dom.fields.SFColorRGBA.prototype.toUint = function ()
{
return ( ( Math.round( this.r * 255 ) << 24 ) |
( Math.round( this.g * 255 ) << 16 ) |
( Math.round( this.b * 255 ) << 8 ) |
Math.round( this.a * 255 ) ) >>> 0;
};
/**
* Sets this color's components from a single integer-encoded value
* holding all channel data in its bits, and returns this color.
*
* The supplied integer number is considered regarding its first 32
* bits, each consecutive eight of which represent the integer value
* of one component in the range of [0, 255]. The keys are as follows:
* - Bits 0 to 7 encode the alpha channel .
* - Bits 8 to 15 encode the blue channel.
* - Bits 16 to 23 encode the green channel.
* - Bits 24 to 31 encode the red channel.
* The format is thus visually:
* RRRRRRRRGGGGGGGGBBBBBBBBAAAAAAAA
*
* @param {Number} rgbaInteger - a 32-bit integer representation of this
* color's new components
* @returns {x3dom.fields.SFColorRGBA} this modified color
*/
x3dom.fields.SFColorRGBA.prototype.setFromUint = function ( rgbaInteger )
{
this.r = ( ( ( rgbaInteger >> 24 ) & 255 ) / 255 );
this.g = ( ( ( rgbaInteger >> 16 ) & 255 ) / 255 );
this.b = ( ( ( rgbaInteger >> 8 ) & 255 ) / 255 );
this.a = ( ( ( rgbaInteger >> 0 ) & 255 ) / 255 );
return this;
};
/**
* Parses a string and creates and returns a color based upon it.
*
* The input string might represent the color information in a variety
* of formats, encompassing a CSS-conformant color name, an invocation
* of ``rgb(red, green, blue)'', an invocation of
* ``rgba(red, green, blue, alpha)'', or a hexadecimal representation of
* the kind ``#RRGGBBAA'', ``#RRGGBB'', ``#RGBA'', or ``#RGB''.
*
* @param {String} color - the string to parse the color information from
* @returns {x3dom.fields.SFColorRGBA} the color parsed from the string,
* or a color with all components
* zero-valued if none format could
* be matched
*/
x3dom.fields.SFColorRGBA.colorParse = function ( color )
{
var rgba = _colorParse( color );
return new x3dom.fields.SFColorRGBA( rgba.r, rgba.g, rgba.b, rgba.a );
};
/**
* SFImage constructor.
*
* Such an image is specified by its width and height in pixels, as well
* as the number of color components, or color depth, and a flat array
* of integer pixel components in the range [0, 255].
*
* For more information consult {@link https://www.web3d.org/documents/specifications/19775-1/V3.2/Part01/fieldsDef.html#SFImageAndMFImage}.
*
* @class Represents an SFImage
*/
x3dom.fields.SFImage = function ( w, h, c, arr )
{
if ( arguments.length === 0 || !( arr && arr.map ) )
{
this.width = 0;
this.height = 0;
this.comp = 0;
this.array = [];
}
else
{
this.width = w;
this.height = h;
this.comp = c;
var that = this.array;
arr.map( function ( v ) { that.push( v ); }, this.array );
}
};
/**
* Parses a string and returns a new image.
*
* The process involves the examination of whitespace-separated tokens
* in the input string, the tally of which design the actual output.
* If at most two tokens are found, the width and height are extracted
* in this order. If three tokens are found, the width, height, and
* number of components are extracted in this order. If four or more
* tokens are found, the width, height, number of components, and the
* available pixel data are extracted in this order.
*
* @param {String} str - the string to parse the image data from
* @returns {x3dom.fields.SFImage} a new image from the parsed string
*/
x3dom.fields.SFImage.parse = function ( str )
{
var img = new x3dom.fields.SFImage();
img.setValueByStr( str );
return img;
};
/**
* Returns a copy of a supplied image.
*
* @param {x3dom.fields.SFImage} that - the image to copy
* @returns {x3dom.fields.SFImage} a copy of the supplied image
*/
x3dom.fields.SFImage.copy = function ( that )
{
var destination = new x3dom.fields.SFImage();
destination.width = that.width;
destination.height = that.height;
destination.comp = that.comp;
// use instead slice?
// destination.array = that.array.slice();
destination.setPixels( that.getPixels() );
return destination;
};
/**
* Returns a copy of this image.
*
* @returns {x3dom.fields.SFImage} a copy of this image
*/
x3dom.fields.SFImage.prototype.copy = function ()
{
return x3dom.fields.SFImage.copy( this );
};
/**
* Parses a string and sets this image's data from it.
*
* The process involves the examination of whitespace-separated tokens
* in the input string, the tally of which design the actual output.
* If at most two tokens are found, the width and height are extracted
* in this order. If three tokens are found, the width, height, and
* number of components are extracted in this order. If four or more
* tokens are found, the width, height, number of components, and the
* available pixel data are extracted in this order.
*
* @param {String} str - the string to parse the image data from
* @returns {x3dom.fields.SFImage} this modified image
*/
x3dom.fields.SFImage.prototype.setValueByStr = function ( str )
{
var mc = str.match( /(\w+)/g );
var n = mc.length;
this.array = [];
if ( n > 2 )
{
this.width = +mc[ 0 ];
this.height = +mc[ 1 ];
this.comp = +mc[ 2 ];
}
else
{
this.width = 0;
this.height = 0;
this.comp = 0;
return;
}
var i,
r,
g,
b,
a;
var radix = 10;
for ( i = 3; i < n; i++ )
{
if ( !mc[ i ].substr ) {continue;}
if ( mc[ i ].substr( 1, 1 ).toLowerCase() === "x" ) {radix = 16;}
// Maybe optimize by directly parsing value!
var inp = parseInt( mc[ i ], radix );
// just coercing should also work:
// var inp = mc[i];
// check for NaN ?
if ( this.comp === 1 )
{
r = inp & 255;
this.array.push( r );
}
else if ( this.comp === 2 )
{
r = inp >> 8 & 255;
g = inp & 255;
this.array.push( r, g );
}
else if ( this.comp === 3 )
{
r = inp >> 16 & 255;
g = inp >> 8 & 255;
b = inp & 255;
this.array.push( r, g, b );
}
else if ( this.comp === 4 )
{
r = inp >> 24 & 255;
g = inp >> 16 & 255;
b = inp >> 8 & 255;
a = inp & 255;
this.array.push( r, g, b, a );
}
}
};
/**
* Sets the pixel color at a specified position of this image.
*
* @param {Number} x - the column index of the pixel to modify, starting
* at zero
* @param {Number} y - the row index of the pixel to modify, starting
* at zero
* @param {x3dom.fields.SFColor} color - the new pixel color
* @returns {x3dom.fields.SFImage} this modified image
*/
x3dom.fields.SFImage.prototype.setPixel = function ( x, y, color )
{
var startIdx = ( y * this.width + x ) * this.comp;
if ( this.comp === 1 && startIdx < this.array.length )
{
this.array[ startIdx ] = color.r * 255;
}
else if ( this.comp === 2 && ( startIdx + 1 ) < this.array.length )
{
this.array[ startIdx ] = color.r * 255;
this.array[ startIdx + 1 ] = color.g * 255;
}
else if ( this.comp === 3 && ( startIdx + 2 ) < this.array.length )
{
this.array[ startIdx ] = color.r * 255;
this.array[ startIdx + 1 ] = color.g * 255;
this.array[ startIdx + 2 ] = color.b * 255;
}
else if ( this.comp === 4 && ( startIdx + 3 ) < this.array.length )
{
this.array[ startIdx ] = color.r * 255;
this.array[ startIdx + 1 ] = color.g * 255;
this.array[ startIdx + 2 ] = color.b * 255;
this.array[ startIdx + 3 ] = color.a * 255;
}
return this;
};
/**
* Returns the pixel at a specified position in this image.
*
* @param {Number} x - the column index of the pixel to return, starting
* at zero
* @param {Number} y - the row index of the pixel to return, starting
* at zero
* @returns {x3dom.fields.SFColorRGBA} the pixel color at the specified
* position
*/
x3dom.fields.SFImage.prototype.getPixel = function ( x, y )
{
var startIdx = ( y * this.width + x ) * this.comp;
if ( this.comp === 1 && startIdx < this.array.length )
{
var intensity = this.array[ startIdx ] / 255;
return new x3dom.fields.SFColorRGBA( intensity,
intensity,
intensity,
1 );
}
else if ( this.comp === 2 && ( startIdx + 1 ) < this.array.length )
{
var intensity = this.array[ startIdx ] / 255;
var alpha = this.array[ startIdx + 1 ] / 255;
return new x3dom.fields.SFColorRGBA( intensity,
intensity,
intensity,
alpha );
}
else if ( this.comp === 3 && ( startIdx + 2 ) < this.array.length )
{
return new x3dom.fields.SFColorRGBA( this.array[ startIdx ] / 255,
this.array[ startIdx + 1 ] / 255,
this.array[ startIdx + 2 ] / 255,
1 );
}
else if ( this.comp === 4 && ( startIdx + 3 ) < this.array.length )
{
return new x3dom.fields.SFColorRGBA( this.array[ startIdx ] / 255,
this.array[ startIdx + 1 ] / 255,
this.array[ startIdx + 2 ] / 255,
this.array[ startIdx + 3 ] / 255 );
}
};
/**
* Sets the pixels from an array of colors.
*
* @param {x3dom.fields.SFColor[]|x3dom.fields.SFColorRGBA[]} pixels -
* an array of the new pixel colors
* @returns {x3dom.fields.SFImage} this modified image
*/
x3dom.fields.SFImage.prototype.setPixels = function ( pixels )
{
var i,
idx = 0;
if ( this.comp === 1 )
{
for ( i = 0; i < pixels.length; i++ )
{
this.array[ idx++ ] = pixels[ i ].r * 255;
}
}
else if ( this.comp === 2 )
{
for ( i = 0; i < pixels.length; i++ )
{
this.array[ idx++ ] = pixels[ i ].r * 255;
this.array[ idx++ ] = pixels[ i ].g * 255;
}
}
else if ( this.comp === 3 )
{
for ( i = 0; i < pixels.length; i++ )
{
this.array[ idx++ ] = pixels[ i ].r * 255;
this.array[ idx++ ] = pixels[ i ].g * 255;
this.array[ idx++ ] = pixels[ i ].b * 255;
}
}
else if ( this.comp === 4 )
{
for ( i = 0; i < pixels.length; i++ )
{
this.array[ idx++ ] = pixels[ i ].r * 255;
this.array[ idx++ ] = pixels[ i ].g * 255;
this.array[ idx++ ] = pixels[ i ].b * 255;
this.array[ idx++ ] = pixels[ i ].a * 255;
}
}
};
/**
* Returns an array with all pixels of this image, each represented by
* an ``SFColorRGBA'' object.
*
* @returns {x3dom.fields.SFColorRGBA[]} an array of RGBA pixel colors
*/
x3dom.fields.SFImage.prototype.getPixels = function ()
{
var i;
var pixels = [];
if ( this.comp === 1 )
{
for ( i = 0; i < this.array.length; i += this.comp )
{
var intensity = this.array[ i ] / 255;
pixels.push( new x3dom.fields.SFColorRGBA( intensity,
intensity,
intensity,
1 ) );
}
}
else if ( this.comp === 2 )
{
for ( i = 0; i < this.array.length; i += this.comp )
{
var intensity = this.array[ i ] / 255;
var alpha = this.array[ i + 1 ] / 255;
pixels.push( new x3dom.fields.SFColorRGBA( intensity,
intensity,
intensity,
alpha ) );
}
}
else if ( this.comp === 3 )
{
for ( i = 0; i < this.array.length; i += this.comp )
{
pixels.push( new x3dom.fields.SFColorRGBA( this.array[ i ] / 255,
this.array[ i + 1 ] / 255,
this.array[ i + 2 ] / 255,
1 ) );
}
}
else if ( this.comp === 4 )
{
for ( i = 0; i < this.array.length; i += this.comp )
{
pixels.push( new x3dom.fields.SFColorRGBA( this.array[ i ] / 255,
this.array[ i + 1 ] / 255,
this.array[ i + 2 ] / 255,
this.array[ i + 3 ] / 255 ) );
}
}
return pixels;
};
/**
* Returns the image pixel data as an array of integer values conforming
* to OpenGL's format.
*
* @returns {Number[]} an array containing this image's pixel data as
* integer components
*/
x3dom.fields.SFImage.prototype.toGL = function ()
{
var a = [];
this.array.map( function ( c )
{
a.push( c );
} );
return a;
};
///////////////////////////////////////////////////////////////////////////////
// Multi-Field Definitions
///////////////////////////////////////////////////////////////////////////////
/**
* MFColor constructor.
*
* An ``MFColorRGBA'' object stores an arbitrary number of
* ``SFColorRGBA'' instances in a one-dimensional array.
*
* @class Represents a MFColor
*/
x3dom.fields.MFColor = function ( colorArray )
{
if ( colorArray )
{
var that = this;
colorArray.map( function ( c ) { that.push( c ); }, this );
}
};
/**
* Returns a copy of the supplied color array.
*
* @param {x3dom.fields.MFColor} colorArray - the color array to copy
* @returns {x3dom.fields.MFColor} a copy of the supplied color array
*/
x3dom.fields.MFColor.copy = function ( colorArray )
{
var destination = new x3dom.fields.MFColor();
colorArray.map( function ( v ) { destination.push( v.copy() ); }, this );
return destination;
};
x3dom.fields.MFColor.prototype = x3dom.extend( [] );
/**
* Parses a string and returns a new RGB color array based upon its
* received color data.
*
* @param {String} str - the string to parse the RGB color data from
* @returns {x3dom.fields.MFColor} a new RGB color array containing the
* parsed RGB colors
*/
x3dom.fields.MFColor.parse = function ( str )
{
var mc = str.match( /([+\-0-9eE\.]+)/g );
var colors = [];
for ( var i = 0, n = mc ? mc.length : 0; i < n; i += 3 )
{
colors.push( new x3dom.fields.SFColor( +mc[ i + 0 ], +mc[ i + 1 ], +mc[ i + 2 ] ) );
}
return new x3dom.fields.MFColor( colors );
};
/**
* Returns a copy of this color array.
*
* @returns {x3dom.fields.MFColor} a copy of this color array
*/
x3dom.fields.MFColor.prototype.copy = function ()
{
return x3dom.fields.MFColor.copy( this );
};
/**
* Parses a string, transfers the extracted color data into this RGB
* color array, and returns the modified RGB color array.
*
* @param {String} str - the string to parse into this RGB color array
* @returns {x3dom.fields.MFColor} this modified RGB color array
*/
x3dom.fields.MFColor.prototype.setValueByStr = function ( str )
{
this.length = 0;
var mc = str.match( /([+\-0-9eE\.]+)/g );
for ( var i = 0, n = mc ? mc.length : 0; i < n; i += 3 )
{
this.push( new x3dom.fields.SFColor( +mc[ i + 0 ], +mc[ i + 1 ], +mc[ i + 2 ] ) );
}
};
/**
* Returns a one-dimensional array of numbers which represents this
* color array's components in order of the maintained colors, being
* suitable for communication with OpenGL.
*
* @returns {Number[]} a one-dimensional array containing all stored
* colors' red, green, and blue components in this
* order
*/
x3dom.fields.MFColor.prototype.toGL = function ()
{
var a = [];
this.map( function ( c )
{
a.push( c.r );
a.push( c.g );
a.push( c.b );
} );
return a;
};
/**
* MFColorRGBA constructor.
*
* An ``MFColorRGBA'' object stores an arbitrary number of
* ``SFColorRGBA'' instances in a one-dimensional array.
*
* @class Represents a MFColorRGBA
*/
x3dom.fields.MFColorRGBA = function ( colorArray )
{
if ( colorArray )
{
var that = this;
colorArray.map( function ( c ) { that.push( c ); }, this );
}
};
/**
* Returns a copy of the supplied RGBA color array.
*
* @param {x3dom.fields.MFColorRGBA} colorArray - the color array to
* copy
* @returns {x3dom.fields.MFColorRGBA} a copy of the supplied color
* array
*/
x3dom.fields.MFColorRGBA.copy = function ( colorArray )
{
var destination = new x3dom.fields.MFColorRGBA();
colorArray.map( function ( v ) { destination.push( v.copy() ); }, this );
return destination;
};
x3dom.fields.MFColorRGBA.prototype = x3dom.extend( [] );
/**
* Parses a string and returns a new RGBA color array from its data.
*
* @param {String} str - the string to parse into an RGBA color array
* @returns {x3dom.fields.MFColorRGBA} a new RGBA color array obtained
* from the parsed string
*/
x3dom.fields.MFColorRGBA.parse = function ( str )
{
var mc = str.match( /([+\-0-9eE\.]+)/g );
var colors = [];
for ( var i = 0, n = mc ? mc.length : 0; i < n; i += 4 )
{
colors.push( new x3dom.fields.SFColorRGBA( +mc[ i + 0 ], +mc[ i + 1 ], +mc[ i + 2 ], +mc[ i + 3 ] ) );
}
return new x3dom.fields.MFColorRGBA( colors );
};
/**
* Returns a copy of this RGBA color array.
*
* @returns {x3dom.fields.MFColorRGBA} a copy of this color array
*/
x3dom.fields.MFColorRGBA.prototype.copy = function ()
{
return x3dom.fields.MFColorRGBA.copy( this );
};
/**
* Parses a string, transfers the extracted color data into this RGBA
* color array, and returns the modified RGBA color array.
*
* @param {String} str - the string to parse into this RGBA color array
* @returns {x3dom.fields.MFColorRGBA} this modified RGBA color array
*/
x3dom.fields.MFColorRGBA.prototype.setValueByStr = function ( str )
{
this.length = 0;
var mc = str.match( /([+\-0-9eE\.]+)/g );
for ( var i = 0, n = mc ? mc.length : 0; i < n; i += 4 )
{
this.push( new x3dom.fields.SFColorRGBA( +mc[ i + 0 ], +mc[ i + 1 ], +mc[ i + 2 ], +mc[ i + 3 ] ) );
}
};
/**
* Returns a one-dimensional array of numbers which represents this
* color array's components in order of the maintained colors, being
* suitable for communication with OpenGL.
*
* @returns {Number[]} a one-dimensional array containing all stored
* colors' red, green, blue, and alpha components in
* this order
*/
x3dom.fields.MFColorRGBA.prototype.toGL = function ()
{
var a = [];
this.map( function ( c )
{
a.push( c.r );
a.push( c.g );
a.push( c.b );
a.push( c.a );
} );
return a;
};
/**
* MFRotation constructor.
*
* An ``MFRotation'' object stores an arbitrary number of
* ``Quaternion'' instances in a one-dimensional array.
*
* @class Represents a MFRotation
*/
x3dom.fields.MFRotation = function ( rotArray )
{
if ( rotArray )
{
var that = this;
rotArray.map( function ( v ) { that.push( v ); }, this );
}
};
x3dom.fields.MFRotation.prototype = x3dom.extend( [] );
/**
* Returns a copy of the specified rotation array.
*
* @param {x3dom.fields.MFRotation} rotationArray - the rotation array
* to copy
* @returns {x3dom.fields.MFRotation} a copy of the supplied rotation
* array
*/
x3dom.fields.MFRotation.copy = function ( rotationArray )
{
var destination = new x3dom.fields.MFRotation();
rotationArray.map( function ( v ) { destination.push( v.copy() ); }, this );
return destination;
};
/**
* Returns a copy of this rotation array.
*
* @returns {x3dom.fields.MFRotation} a copy of this rotation array
*/
x3dom.fields.MFRotation.prototype.copy = function ()
{
return x3dom.fields.MFRotation.copy( this );
};
/**
* Parses a string and returns a new rotation array based upon it.
*
* @param {String} str - the string to parse the rotation array from
* @returns {x3dom.fields.MFRotation} a new rotation array parsed from
* the input string
*/
x3dom.fields.MFRotation.parse = function ( str )
{
var mc = str.match( /([+\-0-9eE\.]+)/g );
var vecs = [];
for ( var i = 0, n = mc ? mc.length : 0; i < n; i += 4 )
{
vecs.push( x3dom.fields.Quaternion.axisAngle( new x3dom.fields.SFVec3f( +mc[ i + 0 ], +mc[ i + 1 ], +mc[ i + 2 ] ), +mc[ i + 3 ] ) );
}
// holds the quaternion representation as needed by interpolators etc.
return new x3dom.fields.MFRotation( vecs );
};
/**
* Parses a string, sets this rotation array's rotation from it, and
* returns this modified rotation array.
*
* @param {String} str - the string to parse the rotations from
* @returns {x3dom.fields.MFRotation} this modified rotation array
*/
x3dom.fields.MFRotation.prototype.setValueByStr = function ( str )
{
this.length = 0;
var mc = str.match( /([+\-0-9eE\.]+)/g );
for ( var i = 0, n = mc ? mc.length : 0; i < n; i += 4 )
{
this.push( x3dom.fields.Quaternion.axisAngle( new x3dom.fields.SFVec3f( +mc[ i + 0 ], +mc[ i + 1 ], +mc[ i + 2 ] ), +mc[ i + 3 ] ) );
}
};
/**
* Returns a one-dimensional array of numbers which represents this
* rotation array's components in order of the maintained rotations,
* being suitable for communication with OpenGL.
*
* @returns {Number[]} a one-dimensional array containing all stored
* rotations' x, y, z, and alpha components in this
* order
*/
x3dom.fields.MFRotation.prototype.toGL = function ()
{
var a = [];
this.map( function ( c )
{
var val = c.toAxisAngle();
a.push( val[ 0 ].x );
a.push( val[ 0 ].y );
a.push( val[ 0 ].z );
a.push( val[ 1 ] );
} );
return a;
};
/**
* MFVec3f constructor.
*
* An ``MFVec3f'' object stores an arbitrary number of
* ``SFVec3f'' instances in a one-dimensional array.
*
* @class Represents a MFVec3f
*/
x3dom.fields.MFVec3f = function ( vec3Array )
{
if ( vec3Array )
{
var that = this;
vec3Array.map( function ( v ) { that.push( v ); }, this );
}
};
x3dom.fields.MFVec3f.prototype = x3dom.extend( Array );
/**
* Returns a copy of the specified 3D vector array.
*
* @param {x3dom.fields.MFVec3f} vecArray - the 3D vector array to copy
* @returns {x3dom.fields.MFVec3f} a copy of the supplied 3D vector
* array
*/
x3dom.fields.MFVec3f.copy = function ( vec3Array )
{
var destination = new x3dom.fields.MFVec3f();
vec3Array.map( function ( v ) { destination.push( v.copy() ); }, this );
return destination;
};
/**
* Parses a string and creates and returns a new 3D vector array.
*
* @param {String} str - the string to parse
* @returns {x3dom.fields.MFVec3f} a new 3D vector array containing
* the parsed vectors
*/
x3dom.fields.MFVec3f.parse = function ( str )
{
var mc = str.match( /([+\-0-9eE\.]+)/g );
var vecs = [];
for ( var i = 0, n = mc ? mc.length : 0; i < n; i += 3 )
{
vecs.push( new x3dom.fields.SFVec3f( +mc[ i + 0 ], +mc[ i + 1 ], +mc[ i + 2 ] ) );
}
return new x3dom.fields.MFVec3f( vecs );
};
/**
* Returns a copy of this vector array.
*
* @returns {x3dom.fields.MFVec3f} a copy of this object
*/
x3dom.fields.MFVec3f.prototype.copy = function ()
{
return x3dom.fields.MFVec3f.copy( this );
};
/**
* Parses a string and sets this 3D vector array's elements from the
* obtained data, finally returning this modified array.
*
* @param {String} str - the string to parse
* @returns {x3dom.fields.MFVec3f} this modified 3D vector array
*/
x3dom.fields.MFVec3f.prototype.setValueByStr = function ( str )
{
this.length = 0;
var mc = str.match( /([+\-0-9eE\.]+)/g );
for ( var i = 0, n = mc ? mc.length : 0; i < n; i += 3 )
{
this.push( new x3dom.fields.SFVec3f( +mc[ i + 0 ], +mc[ i + 1 ], +mc[ i + 2 ] ) );
}
return this;
};
/**
* Sets this 3D vector array's elements from the given array.
*
* @param {Array} vec3Array - an array of ``SFVec3f'' objects
* to copy from
*/
x3dom.fields.MFVec3f.prototype.setValues = function ( vec3Array )
{
var i;
var n = Math.min( vec3Array.length, this.length );
for ( i = 0; i < n; i++ )
{
this[ i ].setValues( vec3Array[ i ] );
}
};
/**
* Returns an OpenGL-conformant array representation of this 3D vector
* array, enlisting each 3D vector's coordinates in order.
*
* @returns {Number[]} an array of numbers containing each vector's
* x-, y-, and z-coordinates in this order
*/
x3dom.fields.MFVec3f.prototype.toGL = function ()
{
var a = [];
this.map( function ( c )
{
a.push( c.x );
a.push( c.y );
a.push( c.z );
} );
return a;
};
/**
* Returns a string representation of this 3D vector array.
*
* @returns {String} a string representation of this 3D vector array
*/
x3dom.fields.MFVec3f.prototype.toString = function ()
{
var str = "";
this.forEach( function ( sf )
{
str = str + sf.toString() + " ";
} );
return str.trim();
};
/**
* MFVec2f constructor.
*
* An ``MFVec2f'' object stores an arbitrary number of
* ``SFVec2f'' instances in a one-dimensional array.
*
* @class Represents a MFVec2f
*/
x3dom.fields.MFVec2f = function ( vec2Array )
{
if ( vec2Array )
{
var that = this;
vec2Array.map( function ( v ) { that.push( v ); }, this );
}
};
x3dom.fields.MFVec2f.prototype = x3dom.extend( [] );
/**
* Returns a copy of the specified 2D vector array.
*
* @param {x3dom.fields.MFVec2f} vec2Array - the 2D vector array to copy
* @returns {x3dom.fields.MFVec2f} a copy of the supplied 2D vector
* array
*/
x3dom.fields.MFVec2f.copy = function ( vec2Array )
{
var destination = new x3dom.fields.MFVec2f();
vec2Array.map( function ( v ) { destination.push( v.copy() ); }, this );
return destination;
};
/**
* Parses a string and creates and returns a new 2D vector array.
*
* @param {String} str - the string to parse
* @returns {x3dom.fields.MFVec3f} a new 2D vector array containing
* the parsed vectors
*/
x3dom.fields.MFVec2f.parse = function ( str )
{
var mc = str.match( /([+\-0-9eE\.]+)/g );
var vecs = [];
for ( var i = 0, n = mc ? mc.length : 0; i < n; i += 2 )
{
vecs.push( new x3dom.fields.SFVec2f( +mc[ i + 0 ], +mc[ i + 1 ] ) );
}
return new x3dom.fields.MFVec2f( vecs );
};
/**
* Returns a copy of this 2D vector array.
*
* @returns {x3dom.fields.MFVec2f} a copy of this 2D vector array
*/
x3dom.fields.MFVec2f.prototype.copy = function ()
{
return x3dom.fields.MFVec2f.copy( this );
};
/**
* Parses a string and sets this 2D vector array's elements from the
* obtained data, finally returning this modified array.
*
* @param {String} str - the string to parse
* @returns {x3dom.fields.MFVec2f} this modified 3D vector array
*/
x3dom.fields.MFVec2f.prototype.setValueByStr = function ( str )
{
this.length = 0;
var mc = str.match( /([+\-0-9eE\.]+)/g );
for ( var i = 0, n = mc ? mc.length : 0; i < n; i += 2 )
{
this.push( new x3dom.fields.SFVec2f( +mc[ i + 0 ], +mc[ i + 1 ] ) );
}
};
/**
* Returns an OpenGL-conformant array representation of this 2D vector
* array, enlisting each 2D vector's coordinates in order.
*
* @returns {Number[]} an array of numbers containing each vector's
* x- and y-coordinates in this order
*/
x3dom.fields.MFVec2f.prototype.toGL = function ()
{
var a = [];
this.map( function ( v )
{
a.push( v.x );
a.push( v.y );
} );
return a;
};
/**
* MFInt32 constructor.
*
* An ``MFInt32'' object stores an arbitrary number of integer
* values in a one-dimensional array.
*
* @class Represents a MFInt32
*/
x3dom.fields.MFInt32 = function ( array )
{
if ( array )
{
var that = this;
array.map( function ( v ) { that.push( v ); }, this );
}
};
x3dom.fields.MFInt32.prototype = x3dom.extend( [] );
/**
* Returns a copy of the supplied integer array.
*
* @param {x3dom.fields.MFInt32} intArray - the integer array to copy
* @returns {x3dom.fields.MFInt32} a copy of the supplied integer array
*/
x3dom.fields.MFInt32.copy = function ( intArray )
{
var destination = new x3dom.fields.MFInt32();
intArray.map( function ( v ) { destination.push( v ); }, this );
return destination;
};
/**
* Parses a string and returns a new integer array containing the
* extracted integer values.
*
* @param {String} str - the string to parse
* @returns {x3dom.fields.MFInt32} a new integer array containing the
* parsed values
*/
x3dom.fields.MFInt32.parse = function ( str )
{
var mc = str.match( /([+\-]?\d+\s*){1},?\s*/g );
var vals = [];
for ( var i = 0, n = mc ? mc.length : 0; i < n; ++i )
{
vals.push( parseInt( mc[ i ], 10 ) );
}
return new x3dom.fields.MFInt32( vals );
};
/**
* Returns a copy of this integer array.
*
* @returns {x3dom.fields.MFInt32} a copy of this integer array
*/
x3dom.fields.MFInt32.prototype.copy = function ()
{
return x3dom.fields.MFInt32.copy( this );
};
/**
* Parses a string and sets this integer array's elements from the
* obtained data, finally returning this modified array.
*
* @param {String} str - the string to parse
* @returns {x3dom.fields.MFInt32} this modified integer array
*/
x3dom.fields.MFInt32.prototype.setValueByStr = function ( str )
{
this.length = 0;
var mc = str.match( /([+\-]?\d+\s*){1},?\s*/g );
for ( var i = 0, n = mc ? mc.length : 0; i < n; ++i )
{
this.push( parseInt( mc[ i ], 10 ) );
}
};
/**
* Returns an OpenGL-conformant array representation of this integer
* array as a one-dimensional array.
*
* @returns {Number[]} an array of numbers containing the integer values
*/
x3dom.fields.MFInt32.prototype.toGL = function ()
{
var a = [];
this.map( function ( v )
{
a.push( v );
} );
return a;
};
/**
* Returns the maximum value in this integer
* array as a number.
*
* @returns {Number} maximum value in array allowing for large arrays
*/
x3dom.fields.MFInt32.prototype.max = function ()
{
return this.reduce( ( a, b ) => Math.max( a, b ), -Infinity );
};
/**
* Returns the minimum value in this integer
* array as a number.
*
* @returns {Number} minimum value in array allowing for large arrays
*/
x3dom.fields.MFInt32.prototype.min = function ()
{
return this.reduce( ( a, b ) => Math.min( a, b ), Infinity );
};
/**
* MFFloat constructor.
*
* An ``MFFloat'' object stores an arbitrary number of
* floating-point numbers in a one-dimensional array.
*
* @class Represents a MFFloat
*/
x3dom.fields.MFFloat = function ( array )
{
if ( array )
{
var that = this;
array.map( function ( v ) { that.push( v ); }, this );
}
};
x3dom.fields.MFFloat.prototype = x3dom.extend( [] );
/**
* Returns a copy of the supplied float array.
*
* @returns {x3dom.fields.MFFloat} a copy of the supplied float array
*/
x3dom.fields.MFFloat.copy = function ( floatArray )
{
var destination = new x3dom.fields.MFFloat();
floatArray.map( function ( v ) { destination.push( v ); }, this );
return destination;
};
/**
* Parses a string and returns a new float array containing the
* extracted floating-point values.
*
* @param {String} str - the string to parse
* @returns {x3dom.fields.MFFloat} a new float array containing the
* parsed values
*/
x3dom.fields.MFFloat.parse = function ( str )
{
var mc = str.match( /([+\-0-9eE\.]+)/g );
var vals = [];
for ( var i = 0, n = mc ? mc.length : 0; i < n; i++ )
{
vals.push( +mc[ i ] );
}
return new x3dom.fields.MFFloat( vals );
};
/**
* Returns a copy of this float array.
*
* @returns {x3dom.fields.MFFloat} a copy of this float array
*/
x3dom.fields.MFFloat.prototype.copy = function ()
{
return x3dom.fields.MFFloat.copy( this );
};
/**
* Parses a string and sets this float array's elements from the
* obtained data, finally returning this modified array.
*
* @param {String} str - the string to parse
* @returns {x3dom.fields.MFFloat} this modified float array
*/
x3dom.fields.MFFloat.prototype.setValueByStr = function ( str )
{
this.length = 0;
var mc = str.match( /([+\-0-9eE\.]+)/g );
for ( var i = 0, n = mc ? mc.length : 0; i < n; i++ )
{
this.push( +mc[ i ] );
}
};
/**
* Returns a one-dimensional array of numbers which represents this
* float array's numbers, being suitable for communication with OpenGL.
*
* @returns {Number[]} a one-dimensional array containing all stored
* floating-point numbers
*/
x3dom.fields.MFFloat.prototype.toGL = function ()
{
var a = [];
this.map( function ( v )
{
a.push( v );
} );
return a;
};
/**
* Returns the maximum value in this float
* array as a number.
*
* @returns {Number} maximum value in array allowing for large arrays
*/
x3dom.fields.MFFloat.prototype.max = function ()
{
return this.reduce( ( a, b ) => Math.max( a, b ), -Infinity );
};
/**
* Returns the minimum value in this float
* array as a number.
*
* @returns {Number} minimum value in array allowing for large arrays
*/
x3dom.fields.MFFloat.prototype.min = function ()
{
return this.reduce( ( a, b ) => Math.min( a, b ), Infinity );
};
/**
* MFBoolean constructor.
*
* An ``MFBoolean'' object stores an arbitrary number of
* ``Boolean'' values in a one-dimensional array.
*
* @class Represents a MFBoolean
*/
x3dom.fields.MFBoolean = function ( array )
{
if ( array )
{
var that = this;
array.map( function ( v ) { that.push( v ); }, this );
}
};
x3dom.fields.MFBoolean.prototype = x3dom.extend( [] );
/**
* Returns a copy of the supplied Boolean array.
*
* @returns {x3dom.fields.MFBoolean} the copy of the supplied Boolean
* array
*/
x3dom.fields.MFBoolean.copy = function ( boolArray )
{
var destination = new x3dom.fields.MFBoolean();
boolArray.map( function ( v ) { destination.push( v ); }, this );
return destination;
};
/**
* Parses a string and returns a new Boolean array containing the
* extracted Boolean values.
*
* @param {String} str - the string to parse
* @returns {x3dom.fields.MFBoolean} a new boolean array containing the
* parsed values
*/
x3dom.fields.MFBoolean.parse = function ( str )
{
var mc = str.match( /(true|false|1|0)/ig );
var vals = [];
for ( var i = 0, n = mc ? mc.length : 0; i < n; i++ )
{
vals.push( ( mc[ i ] == "1" || mc[ i ].toLowerCase() == "true" ) );
}
return new x3dom.fields.MFBoolean( vals );
};
/**
* Returns a copy of this Boolean array.
*
* @returns {Boolean} a copy of this Boolean array
*/
x3dom.fields.MFBoolean.prototype.copy = function ()
{
return x3dom.fields.MFBoolean.copy( this );
};
/**
* Parses a string and sets this Boolean array's elements from the
* obtained data, finally returning this modified array.
*
* @param {String} str - the string to parse
* @returns {x3dom.fields.MFBoolean} this modified Boolean array
*/
x3dom.fields.MFBoolean.prototype.setValueByStr = function ( str )
{
this.length = 0;
var mc = str.match( /(true|false|1|0)/ig );
for ( var i = 0, n = mc ? mc.length : 0; i < n; i++ )
{
this.push( ( mc[ i ] == "1" || mc[ i ].toLowerCase() == "true" ) );
}
};
/**
* Returns a one-dimensional array of integer numbers which represents
* this Boolean array's truth values, being suitable for communication
* with OpenGL.
*
* Each Boolean ``true'' value will be converted into the
* integer one (1), each ``false'' into the integer zero (0).
*
* @returns {Number[]} a one-dimensional array representing the Boolean
* truth values in a numerical fashion
*/
x3dom.fields.MFBoolean.prototype.toGL = function ()
{
var a = [];
this.map( function ( v )
{
a.push( v ? 1 : 0 );
} );
return a;
};
/**
* MFString constructor.
*
* An ``MFString'' object stores an arbitrary number of
* ``String'' values in a one-dimensional array.
* @class Represents a MFString
*/
x3dom.fields.MFString = function ( strArray )
{
if ( strArray && strArray.map )
{
var that = this;
strArray.map( function ( v ) { that.push( v ); }, this );
}
};
x3dom.fields.MFString.prototype = x3dom.extend( [] );
/**
* Creates and returns a copy of the supplied string array.
*
* @param {MFString} stringArray - the string array to copy
* @returns {MFString} a copy of the input string array
*/
x3dom.fields.MFString.copy = function ( stringArray )
{
var destination = new x3dom.fields.MFString();
stringArray.map( function ( v ) { destination.push( v ); }, this );
return destination;
};
/**
* Parses a string and returns a new string array containing the
* extracted string values.
*
* @param {String} str - the string to parse
* @returns {x3dom.fields.MFString} a new string array containing the
* parsed values
*/
x3dom.fields.MFString.parse = function ( str )
{
var arr = [];
str = str.trim();
// ignore leading whitespace?
if ( str.length && str[ 0 ] == "\"" )
{
var m;
var re = /"((?:[^\\"]|\\\\|\\")*)"/g;
while ( ( m = re.exec( str ) ) )
{
var s = m[ 1 ].replace( /\\([\\"])/g, "$1" );
if ( s !== undefined )
{
arr.push( s );
}
}
}
else
{
arr.push( str );
}
return new x3dom.fields.MFString( arr );
};
/**
* Returns a copy of this string array.
*
* @returns {MFString} a copy of this string array
*/
x3dom.fields.MFString.prototype.copy = function ()
{
return x3dom.fields.MFString.copy( this );
};
/**
* Parses a string and sets this string array's elements from the
* obtained data, finally returning this modified array.
*
* @param {String} str - the string to parse
* @returns {x3dom.fields.MFString} this modified string array
*/
x3dom.fields.MFString.prototype.setValueByStr = function ( str )
{
this.length = 0;
// ignore leading whitespace?
if ( str.length && str[ 0 ] == "\"" )
{
var m;
var re = /"((?:[^\\"]|\\\\|\\")*)"/g;
while ( ( m = re.exec( str ) ) )
{
var s = m[ 1 ].replace( /\\([\\"])/, "$1" );
if ( s !== undefined )
{
this.push( s );
}
}
}
else
{
this.push( str );
}
return this;
};
/**
* Returns a string representation of this string array.
*
* @returns {String} a string representation of this string array
*/
x3dom.fields.MFString.prototype.toString = function ()
{
var str = "";
for ( var i = 0, n = this.length; i < n; i++ )
{
str = str + this[ i ] + " ";
}
return str;
};
///////////////////////////////////////////////////////////////////////////////
// Single-/Multi-Field Node Definitions
///////////////////////////////////////////////////////////////////////////////
/**
* SFNode constructor.
*
* @class Represents a SFNode
*/
x3dom.fields.SFNode = function ( type )
{
this.type = type;
this.node = null;
};
/**
* Checks whether this node refers to the specified one.
*
* @param {x3dom.fields.SFNode} node - the node to check for presence
* @returns {Boolean} ``true'' if this node contains the
* supplied one, ``false'' otherwise
*/
x3dom.fields.SFNode.prototype.hasLink = function ( node )
{
return ( node ? ( this.node === node ) : this.node );
};
/**
* Stores the specified node in this one, always returning the Boolean
* ``true'' value as a sign of success.
*
* @param {x3dom.fields.SFNode} node - the node to add
* @returns {Boolean} always ``true'' as a sign of successful
* storage of the supplied node
*/
x3dom.fields.SFNode.prototype.addLink = function ( node )
{
this.node = node;
return true;
};
/**
* Removes the specified node from this one, if it matches the stored
* data, returning a Boolean success or failure value.
*
* @param {x3dom.fields.SFNode} node - the node to remove
* @returns {Boolean} ``true'' if the specified node was
* stored in this one and could be removed,
* ``false'' if not
*/
x3dom.fields.SFNode.prototype.rmLink = function ( node )
{
if ( this.node === node )
{
this.node = null;
return true;
}
else
{
return false;
}
};
/**
* MFNode constructor.
*
* Represents a collection of zero or more ``SFNode'' objects,
* thus being a node array.
*
* @class Represents a MFNode
*/
x3dom.fields.MFNode = function ( type )
{
this.type = type;
this.nodes = [];
};
/**
* Checks whether this node array contains the specified node, returning
* a Boolean check result.
*
* @param {x3dom.fields.SFNode} node - the node to check for presence
* @returns {Boolean} ``true'' if either the specified node is
* contained in this node array, or if no node is
* supplied and the node array is not empty,
* ``false'' if the node is either not part
* of this node array, or is not supplied and the
* node array is empty
*/
x3dom.fields.MFNode.prototype.hasLink = function ( node )
{
if ( node )
{
for ( var i = 0, n = this.nodes.length; i < n; i++ )
{
if ( this.nodes[ i ] === node )
{
return true;
}
}
}
else
{
return ( this.length > 0 );
}
return false;
};
/**
* Adds the specified node to this node's children
*
* @param {SFNode} node - the node to append
* @returns {Boolean} always ``true''
*/
x3dom.fields.MFNode.prototype.addLink = function ( node )
{
this.nodes.push( node );
return true;
};
/**
* Removes the first occurrence of a specified node from the child
* nodes list of this node, returning a Boolean success or failure flag.
*
* @param {x3dom.fields.SFNode} node - the node to remove
* @returns {Boolean} ``true'' if the node could be found and
* removed, ``false'' if not
*/
x3dom.fields.MFNode.prototype.rmLink = function ( node )
{
for ( var i = 0, n = this.nodes.length; i < n; i++ )
{
if ( this.nodes[ i ] === node )
{
this.nodes.splice( i, 1 );
return true;
}
}
return false;
};
/**
* Returns the number of child nodes stored in this node.
*
* @returns {Number} the number of child nodes
*/
x3dom.fields.MFNode.prototype.length = function ()
{
return this.nodes.length;
};
///////////////////////////////////////////////////////////////////////////////
// Math Helper Class Definitions
///////////////////////////////////////////////////////////////////////////////
/**
* Line constructor.
*
* @param {SFVec3f} pos - anchor point of the line
* @param {SFVec3f} dir - direction of the line, must be normalized
* @class Represents a Line (as internal helper).
* A line has an origin and a vector that describes a direction,
* it is infinite in both directions.
*/
x3dom.fields.Line = function ( pos, dir )
{
if ( arguments.length === 0 )
{
this.pos = new x3dom.fields.SFVec3f( 0, 0, 0 );
this.dir = new x3dom.fields.SFVec3f( 0, 0, 1 );
}
this.pos = x3dom.fields.SFVec3f.copy( pos );
this.dir = x3dom.fields.SFVec3f.copy( dir );
};
/**
* For a given point, this function returns the closest point on this
* line.
*
* @param p {x3dom.fields.SFVec3f} - the point
* @returns {x3dom.fields.SFVec3f} the closest point on this line
*/
x3dom.fields.Line.prototype.closestPoint = function ( p )
{
var distVec = p.subtract( this.pos );
// project the distance vector on the line
var projDist = distVec.dot( this.dir );
return this.pos.add( this.dir.multiply( projDist ) );
};
/**
* For a given point, this function returns the distance to the closest
* point on this line.
*
* @param p {x3dom.fields.SFVec3f} - the point
* @returns {Number} the distance to the closest point
*/
x3dom.fields.Line.prototype.shortestDistance = function ( p )
{
var distVec = p.subtract( this.pos );
// project the distance vector on the line
var projDist = distVec.dot( this.dir );
// subtract the projected distance vector, to obtain the part that
// is orthogonal to this line
return distVec.subtract( this.dir.multiply( projDist ) ).length();
};
/**
* Ray constructor.
*
* @param {SFVec3f} pos - anchor point of the ray
* @param {SFVec3f} dir - direction of the ray, must be normalized
* @class Represents a Ray (as internal helper).
* A ray is a special line that extends to only one direction
* from its origin.
*/
x3dom.fields.Ray = function ( pos, dir )
{
if ( arguments.length === 0 )
{
this.pos = new x3dom.fields.SFVec3f( 0, 0, 0 );
this.dir = new x3dom.fields.SFVec3f( 0, 0, 1 );
}
else
{
this.pos = new x3dom.fields.SFVec3f( pos.x, pos.y, pos.z );
var n = dir.length();
if ( n ) { n = 1.0 / n; }
this.dir = new x3dom.fields.SFVec3f( dir.x * n,
dir.y * n,
dir.z * n );
}
this.enter = 0;
this.exit = 0;
this.hitObject = null;
this.hitPoint = {};
this.dist = Number.MAX_VALUE;
};
/**
* Returns a string representation of this ray.
*
* @returns {String} a string representation of this ray
*/
x3dom.fields.Ray.prototype.toString = function ()
{
return "Ray: [" + this.pos.toString() + "; " +
this.dir.toString() + "]";
};
/**
* Intersects this ray with a plane, defined by the given anchor point
* and normal. The result returned is the point of intersection, if any.
* If no point of intersection exists, null is returned. Null is also
* returned in case there is an infinite number of solutions (, i.e., if
* the ray origin lies in the plane).
*
* @param {x3dom.fields.SFVec3f} p - anchor point
* @param {x3dom.fields.SFVec3f} n - plane normal
* @returns {x3dom.fields.SFVec3f} the point of intersection, can be null
*/
x3dom.fields.Ray.prototype.intersectPlane = function ( p, n )
{
var result = null;
var alpha; // ray parameter, should be computed
var nDotDir = n.dot( this.dir );
// if the ray hits the plane, the plane normal and ray direction
// must be facing each other
if ( nDotDir < 0.0 )
{
alpha = ( p.dot( n ) - this.pos.dot( n ) ) / nDotDir;
result = this.pos.addScaled( this.dir, alpha );
}
return result;
};
/**
* Intersect a line with a box volume specified by its the low and
* high points and demarcations of its main diagonal.
*
* @param {x3dom.fields.SFVec3f} the lower endpoint of the box diagonal
* @param {x3dom.fields.SFVec3f} the higher endpoint of the box diagonal
*/
x3dom.fields.Ray.prototype.intersect = function ( low, high )
{
var isect = 0.0;
var out = Number.MAX_VALUE;
var r;
var te;
var tl;
if ( this.dir.x > x3dom.fields.Eps )
{
r = 1.0 / this.dir.x;
te = ( low.x - this.pos.x ) * r;
tl = ( high.x - this.pos.x ) * r;
if ( tl < out )
{
out = tl;
}
if ( te > isect )
{
isect = te;
}
}
else if ( this.dir.x < -x3dom.fields.Eps )
{
r = 1.0 / this.dir.x;
te = ( high.x - this.pos.x ) * r;
tl = ( low.x - this.pos.x ) * r;
if ( tl < out )
{
out = tl;
}
if ( te > isect )
{
isect = te;
}
}
else if ( this.pos.x < low.x || this.pos.x > high.x )
{
return false;
}
if ( this.dir.y > x3dom.fields.Eps )
{
r = 1.0 / this.dir.y;
te = ( low.y - this.pos.y ) * r;
tl = ( high.y - this.pos.y ) * r;
if ( tl < out )
{
out = tl;
}
if ( te > isect )
{
isect = te;
}
if ( isect - out >= x3dom.fields.Eps )
{
return false;
}
}
else if ( this.dir.y < -x3dom.fields.Eps )
{
r = 1.0 / this.dir.y;
te = ( high.y - this.pos.y ) * r;
tl = ( low.y - this.pos.y ) * r;
if ( tl < out )
{
out = tl;
}
if ( te > isect )
{
isect = te;
}
if ( isect - out >= x3dom.fields.Eps )
{
return false;
}
}
else if ( this.pos.y < low.y || this.pos.y > high.y )
{
return false;
}
if ( this.dir.z > x3dom.fields.Eps )
{
r = 1.0 / this.dir.z;
te = ( low.z - this.pos.z ) * r;
tl = ( high.z - this.pos.z ) * r;
if ( tl < out )
{
out = tl;
}
if ( te > isect )
{
isect = te;
}
}
else if ( this.dir.z < -x3dom.fields.Eps )
{
r = 1.0 / this.dir.z;
te = ( high.z - this.pos.z ) * r;
tl = ( low.z - this.pos.z ) * r;
if ( tl < out )
{
out = tl;
}
if ( te > isect )
{
isect = te;
}
}
else if ( this.pos.z < low.z || this.pos.z > high.z )
{
return false;
}
this.enter = isect;
this.exit = out;
return ( isect - out < x3dom.fields.Eps );
};
/**
* BoxVolume constructor.
*
* @class Represents a box volume (as internal helper).
*/
x3dom.fields.BoxVolume = function ( min, max )
{
if ( arguments.length < 2 )
{
this.min = new x3dom.fields.SFVec3f( 0, 0, 0 );
this.max = new x3dom.fields.SFVec3f( 0, 0, 0 );
this.valid = false;
}
else
{
// compiler enforced type check for min/max would be nice
this.min = x3dom.fields.SFVec3f.copy( min );
this.max = x3dom.fields.SFVec3f.copy( max );
this.valid = true;
}
this.updateInternals();
};
/**
* Box Volume Get Scalar Value
*
* @returns {number}
*/
x3dom.fields.BoxVolume.prototype.getScalarValue = function ()
{
var extent = this.max.subtract( this.min );
return ( extent.x * extent.y * extent.z );
};
/**
* Box Volume Copy
*
* @param other
* @returns {x3dom.fields.BoxVolume}
*/
x3dom.fields.BoxVolume.copy = function ( other )
{
var volume = new x3dom.fields.BoxVolume( other.min, other.max );
volume.valid = other.valid;
return volume;
};
/**
* Box Volume Equals
*
* @param other
* @returns {*}
*/
x3dom.fields.BoxVolume.prototype.equals = function ( other )
{
return ( this.min.equals( other.min, 0.000000000001 ) &&
this.max.equals( other.max, 0.000000000001 ) );
};
/**
* Box Volume Update Internals
*
*/
x3dom.fields.BoxVolume.prototype.updateInternals = function ()
{
this.radialVec = this.max.subtract( this.min ).multiply( 0.5 );
this.center = this.min.add( this.radialVec );
this.diameter = 2 * this.radialVec.length();
};
/**
* Box Volume Set Bounds
*
* @param min
* @param max
*/
x3dom.fields.BoxVolume.prototype.setBounds = function ( min, max )
{
this.min.setValues( min );
this.max.setValues( max );
this.updateInternals();
this.valid = true;
};
/**
* Box Volume Set Bounds By Center Size
*
* @param center
* @param size
*/
x3dom.fields.BoxVolume.prototype.setBoundsByCenterSize = function ( center,
size )
{
var halfSize = size.multiply( 0.5 );
this.min = center.subtract( halfSize );
this.max = center.add( halfSize );
this.updateInternals();
this.valid = true;
};
/**
* Box Volume Extend Bounds
*
* @param min
* @param max
*/
x3dom.fields.BoxVolume.prototype.extendBounds = function ( min, max )
{
if ( this.valid )
{
if ( this.min.x > min.x ) { this.min.x = min.x; }
if ( this.min.y > min.y ) { this.min.y = min.y; }
if ( this.min.z > min.z ) { this.min.z = min.z; }
if ( this.max.x < max.x ) { this.max.x = max.x; }
if ( this.max.y < max.y ) { this.max.y = max.y; }
if ( this.max.z < max.z ) { this.max.z = max.z; }
this.updateInternals();
}
else
{
this.setBounds( min, max );
}
};
/**
* Box Volume Get Bounds
*
* @param min
* @param max
*/
x3dom.fields.BoxVolume.prototype.getBounds = function ( min, max )
{
min.setValues( this.min );
max.setValues( this.max );
};
/**
* Box Volume Get Radial Vector
*
* @returns {*}
*/
x3dom.fields.BoxVolume.prototype.getRadialVec = function ()
{
return this.radialVec;
};
/**
* Box Volume Invalidate
*
*/
x3dom.fields.BoxVolume.prototype.invalidate = function ()
{
this.valid = false;
this.min = new x3dom.fields.SFVec3f( 0, 0, 0 );
this.max = new x3dom.fields.SFVec3f( 0, 0, 0 );
this.updateInternals();
};
/**
* Box Volume Is Valid?
*
* @returns {Boolean} ``true'' if this box volume is valid,
* ``false'' otherwise
*/
x3dom.fields.BoxVolume.prototype.isValid = function ()
{
return this.valid;
};
/**
* Box Volume Get Center
*
* @returns {*}
*/
x3dom.fields.BoxVolume.prototype.getCenter = function ()
{
return this.center;
};
/**
* Box Volume Get Diameter
*
* @returns {number|*}
*/
x3dom.fields.BoxVolume.prototype.getDiameter = function ()
{
return this.diameter;
};
/**
* Box Volume Transform
*
* @param m
*/
x3dom.fields.BoxVolume.prototype.transform = function ( m )
{
var xmin,
ymin,
zmin,
xmax,
ymax,
zmax;
xmin = xmax = m._03;
ymin = ymax = m._13;
zmin = zmax = m._23;
// calculate xmin and xmax of new transformed BBox
var a = this.max.x * m._00;
var b = this.min.x * m._00;
if ( a >= b )
{
xmax += a;
xmin += b;
}
else
{
xmax += b;
xmin += a;
}
a = this.max.y * m._01;
b = this.min.y * m._01;
if ( a >= b )
{
xmax += a;
xmin += b;
}
else
{
xmax += b;
xmin += a;
}
a = this.max.z * m._02;
b = this.min.z * m._02;
if ( a >= b )
{
xmax += a;
xmin += b;
}
else
{
xmax += b;
xmin += a;
}
// calculate ymin and ymax of new transformed BBox
a = this.max.x * m._10;
b = this.min.x * m._10;
if ( a >= b )
{
ymax += a;
ymin += b;
}
else
{
ymax += b;
ymin += a;
}
a = this.max.y * m._11;
b = this.min.y * m._11;
if ( a >= b )
{
ymax += a;
ymin += b;
}
else
{
ymax += b;
ymin += a;
}
a = this.max.z * m._12;
b = this.min.z * m._12;
if ( a >= b )
{
ymax += a;
ymin += b;
}
else
{
ymax += b;
ymin += a;
}
// calculate zmin and zmax of new transformed BBox
a = this.max.x * m._20;
b = this.min.x * m._20;
if ( a >= b )
{
zmax += a;
zmin += b;
}
else
{
zmax += b;
zmin += a;
}
a = this.max.y * m._21;
b = this.min.y * m._21;
if ( a >= b )
{
zmax += a;
zmin += b;
}
else
{
zmax += b;
zmin += a;
}
a = this.max.z * m._22;
b = this.min.z * m._22;
if ( a >= b )
{
zmax += a;
zmin += b;
}
else
{
zmax += b;
zmin += a;
}
this.min.x = xmin;
this.min.y = ymin;
this.min.z = zmin;
this.max.x = xmax;
this.max.y = ymax;
this.max.z = zmax;
this.updateInternals();
};
/**
* Box Volume Transform Form
*
* @param m
* @param other
*/
x3dom.fields.BoxVolume.prototype.transformFrom = function ( m, other )
{
var xmin,
ymin,
zmin,
xmax,
ymax,
zmax;
xmin = xmax = m._03;
ymin = ymax = m._13;
zmin = zmax = m._23;
// calculate xmin and xmax of new transformed BBox
var a = other.max.x * m._00;
var b = other.min.x * m._00;
if ( a >= b )
{
xmax += a;
xmin += b;
}
else
{
xmax += b;
xmin += a;
}
a = other.max.y * m._01;
b = other.min.y * m._01;
if ( a >= b )
{
xmax += a;
xmin += b;
}
else
{
xmax += b;
xmin += a;
}
a = other.max.z * m._02;
b = other.min.z * m._02;
if ( a >= b )
{
xmax += a;
xmin += b;
}
else
{
xmax += b;
xmin += a;
}
// calculate ymin and ymax of new transformed BBox
a = other.max.x * m._10;
b = other.min.x * m._10;
if ( a >= b )
{
ymax += a;
ymin += b;
}
else
{
ymax += b;
ymin += a;
}
a = other.max.y * m._11;
b = other.min.y * m._11;
if ( a >= b )
{
ymax += a;
ymin += b;
}
else
{
ymax += b;
ymin += a;
}
a = other.max.z * m._12;
b = other.min.z * m._12;
if ( a >= b )
{
ymax += a;
ymin += b;
}
else
{
ymax += b;
ymin += a;
}
// calculate zmin and zmax of new transformed BBox
a = other.max.x * m._20;
b = other.min.x * m._20;
if ( a >= b )
{
zmax += a;
zmin += b;
}
else
{
zmax += b;
zmin += a;
}
a = other.max.y * m._21;
b = other.min.y * m._21;
if ( a >= b )
{
zmax += a;
zmin += b;
}
else
{
zmax += b;
zmin += a;
}
a = other.max.z * m._22;
b = other.min.z * m._22;
if ( a >= b )
{
zmax += a;
zmin += b;
}
else
{
zmax += b;
zmin += a;
}
this.min.x = xmin;
this.min.y = ymin;
this.min.z = zmin;
this.max.x = xmax;
this.max.y = ymax;
this.max.z = zmax;
this.updateInternals();
this.valid = true;
};
/**
* FrustumVolume constructor.
*
* A ``FrustumVolume'' represents the concept of a truncated pyramid
* shape with particular emphasis on its use as a boundary in which
* intersections might occur. The frustum constitutes a very significant
* entity by its connotation with perspectives.
*
* @param {SFMatrix4f} clipMat - a matrix from which to set the
* boundaries (clipping) of this frustum
* @class Represents a frustum (as internal helper).
*/
x3dom.fields.FrustumVolume = function ( clipMat )
{
this.planeNormals = [];
this.planeDistances = [];
this.directionIndex = [];
if ( arguments.length === 0 )
{
return;
}
var planeEquation = [];
for ( var i = 0; i < 6; i++ )
{
this.planeNormals[ i ] = new x3dom.fields.SFVec3f( 0, 0, 0 );
this.planeDistances[ i ] = 0;
this.directionIndex[ i ] = 0;
planeEquation[ i ] = new x3dom.fields.SFVec4f( 0, 0, 0, 0 );
}
planeEquation[ 0 ].x = clipMat._30 - clipMat._00;
planeEquation[ 0 ].y = clipMat._31 - clipMat._01;
planeEquation[ 0 ].z = clipMat._32 - clipMat._02;
planeEquation[ 0 ].w = clipMat._33 - clipMat._03;
planeEquation[ 1 ].x = clipMat._30 + clipMat._00;
planeEquation[ 1 ].y = clipMat._31 + clipMat._01;
planeEquation[ 1 ].z = clipMat._32 + clipMat._02;
planeEquation[ 1 ].w = clipMat._33 + clipMat._03;
planeEquation[ 2 ].x = clipMat._30 + clipMat._10;
planeEquation[ 2 ].y = clipMat._31 + clipMat._11;
planeEquation[ 2 ].z = clipMat._32 + clipMat._12;
planeEquation[ 2 ].w = clipMat._33 + clipMat._13;
planeEquation[ 3 ].x = clipMat._30 - clipMat._10;
planeEquation[ 3 ].y = clipMat._31 - clipMat._11;
planeEquation[ 3 ].z = clipMat._32 - clipMat._12;
planeEquation[ 3 ].w = clipMat._33 - clipMat._13;
planeEquation[ 4 ].x = clipMat._30 + clipMat._20;
planeEquation[ 4 ].y = clipMat._31 + clipMat._21;
planeEquation[ 4 ].z = clipMat._32 + clipMat._22;
planeEquation[ 4 ].w = clipMat._33 + clipMat._23;
planeEquation[ 5 ].x = clipMat._30 - clipMat._20;
planeEquation[ 5 ].y = clipMat._31 - clipMat._21;
planeEquation[ 5 ].z = clipMat._32 - clipMat._22;
planeEquation[ 5 ].w = clipMat._33 - clipMat._23;
for ( i = 0; i < 6; i++ )
{
var vectorLength = Math.sqrt(
planeEquation[ i ].x * planeEquation[ i ].x +
planeEquation[ i ].y * planeEquation[ i ].y +
planeEquation[ i ].z * planeEquation[ i ].z );
planeEquation[ i ].x /= vectorLength;
planeEquation[ i ].y /= vectorLength;
planeEquation[ i ].z /= vectorLength;
planeEquation[ i ].w /= -vectorLength;
}
var updateDirectionIndex = function ( normalVec )
{
var ind = 0;
if ( normalVec.x > 0 ) {ind |= 1;}
if ( normalVec.y > 0 ) {ind |= 2;}
if ( normalVec.z > 0 ) {ind |= 4;}
return ind;
};
// right
this.planeNormals[ 3 ].setValues( planeEquation[ 0 ] );
this.planeDistances[ 3 ] = planeEquation[ 0 ].w;
this.directionIndex[ 3 ] = updateDirectionIndex( this.planeNormals[ 3 ] );
// left
this.planeNormals[ 2 ].setValues( planeEquation[ 1 ] );
this.planeDistances[ 2 ] = planeEquation[ 1 ].w;
this.directionIndex[ 2 ] = updateDirectionIndex( this.planeNormals[ 2 ] );
// bottom
this.planeNormals[ 5 ].setValues( planeEquation[ 2 ] );
this.planeDistances[ 5 ] = planeEquation[ 2 ].w;
this.directionIndex[ 5 ] = updateDirectionIndex( this.planeNormals[ 5 ] );
// top
this.planeNormals[ 4 ].setValues( planeEquation[ 3 ] );
this.planeDistances[ 4 ] = planeEquation[ 3 ].w;
this.directionIndex[ 4 ] = updateDirectionIndex( this.planeNormals[ 4 ] );
// near
this.planeNormals[ 0 ].setValues( planeEquation[ 4 ] );
this.planeDistances[ 0 ] = planeEquation[ 4 ].w;
this.directionIndex[ 0 ] = updateDirectionIndex( this.planeNormals[ 0 ] );
// far
this.planeNormals[ 1 ].setValues( planeEquation[ 5 ] );
this.planeDistances[ 1 ] = planeEquation[ 5 ].w;
this.directionIndex[ 1 ] = updateDirectionIndex( this.planeNormals[ 1 ] );
};
/**
* Check the volume against the frustum.
* Return values > 0 indicate a plane mask that was used to identify
* the object as "inside".
* Return value -1 means the object has been culled (i.e., is outside
* with respect to at least one plane).
* Return value 0 is a rare case, indicating that the object intersects
* with all planes of the frustum.
*
* @param vol
* @param planeMask
*/
x3dom.fields.FrustumVolume.prototype.intersect = function ( vol, planeMask )
{
if ( this.planeNormals.length < 6 )
{
x3dom.debug.logWarning( "FrustumVolume not initialized!" );
return false;
}
var that = this;
var min = vol.min;
var max = vol.max;
var setDirectionIndexPoint = function ( index )
{
var pnt = new x3dom.fields.SFVec3f( 0, 0, 0 );
if ( index & 1 ) { pnt.x = min.x; }
else { pnt.x = max.x; }
if ( index & 2 ) { pnt.y = min.y; }
else { pnt.y = max.y; }
if ( index & 4 ) { pnt.z = min.z; }
else { pnt.z = max.z; }
return pnt;
};
// Check if the point is in the halfspace
var pntIsInHalfSpace = function ( i, pnt )
{
var s = that.planeNormals[ i ].dot( pnt ) - that.planeDistances[ i ];
return ( s >= 0 );
};
// Check if the box formed by min/max is fully inside the halfspace
var isInHalfSpace = function ( i )
{
var p = setDirectionIndexPoint( that.directionIndex[ i ] );
return pntIsInHalfSpace( i, p );
};
// Check if the box formed by min/max is fully outside the halfspace
var isOutHalfSpace = function ( i )
{
var p = setDirectionIndexPoint( that.directionIndex[ i ] ^ 7 );
return !pntIsInHalfSpace( i, p );
};
// Check each point of the box to the 6 planes
var mask = 1;
if ( planeMask < 0 ) {planeMask = 0;}
for ( var i = 0; i < 6; i++, mask <<= 1 )
{
if ( ( planeMask & mask ) != 0 )
{continue;}
if ( isOutHalfSpace( i ) )
{return -1;}
if ( isInHalfSpace( i ) )
{planeMask |= mask;}
}
return planeMask;
};
/**
* X3DOM JavaScript Library
* http://www.x3dom.org
*
* (C)2009 Fraunhofer IGD, Darmstadt, Germany
* Dual licensed under the MIT and GPL
*
* Based on code originally provided by
* Philip Taylor: http://philip.html5.org
*/
/**
* BindableStack constructor
*
* @param doc
* @param type
* @param defaultType
* @param getter
*
* @constructor
*/
x3dom.BindableStack = function ( doc, type, defaultType, getter )
{
this._doc = doc;
this._type = type;
this._defaultType = defaultType;
this._defaultRoot = null;
this._getter = getter;
this._bindBag = [];
this._bindStack = [];
};
/**
* Bindable Stack Top
*
* @returns {number|null}
*/
x3dom.BindableStack.prototype.top = function ()
{
return ( ( this._bindStack.length > 0 ) ? this._bindStack[ this._bindStack.length - 1 ] : null );
};
/**
* Bindable Stack Push
*
* @param bindable
*/
x3dom.BindableStack.prototype.push = function ( bindable )
{
var top = this.top();
if ( top === bindable )
{
return;
}
if ( top )
{
top.deactivate();
}
this._bindStack.push( bindable );
bindable.activate( top );
};
/**
* Bindable Stack Replace Top
*
* @param bindable
*/
x3dom.BindableStack.prototype.replaceTop = function ( bindable )
{
var top = this.top();
if ( top === bindable )
{
return;
}
if ( top )
{
top.deactivate();
this._bindStack[ this._bindStack.length - 1 ] = bindable;
bindable.activate( top );
}
};
/**
* Bindable Stack Replace Pop
*
* @param bindable
* @returns {*}
*/
x3dom.BindableStack.prototype.pop = function ( bindable )
{
var top;
if ( bindable )
{
top = this.top();
if ( bindable !== top )
{
return null;
}
}
top = this._bindStack.pop();
if ( top )
{
top.deactivate();
}
return top;
};
/**
* Bindable Stack Replace Switch To
*
* @param target
*/
x3dom.BindableStack.prototype.switchTo = function ( target )
{
var last = this.getActive();
var n = this._bindBag.length;
var toBind = 0;
var i = 0,
lastIndex = -1;
if ( n <= 1 )
{
return;
}
switch ( target )
{
case "first":
toBind = this._bindBag[ 0 ];
break;
case "last":
toBind = this._bindBag[ n - 1 ];
break;
default:
for ( i = 0; i < n; i++ )
{
if ( this._bindBag[ i ] == last )
{
lastIndex = i;
break;
}
}
if ( lastIndex >= 0 )
{
i = lastIndex;
while ( !toBind )
{
if ( target == "next" )
{
i = ( i < ( n - 1 ) ) ? ( i + 1 ) : 0;
}
else
{ // prev
i = ( i > 0 ) ? ( i - 1 ) : ( n - 1 );
}
if ( i == lastIndex )
{
break;
}
if ( this._bindBag[ i ]._vf.description.length >= 0 )
{
toBind = this._bindBag[ i ];
}
}
}
break;
}
if ( toBind )
{
this.replaceTop( toBind );
}
else
{
x3dom.debug.logWarning( "Cannot switch bindable; no other bindable with description found." );
}
};
/**
* Get currently active bindable of given stack type, creates new if none exists
*
* @returns {*}
*/
x3dom.BindableStack.prototype.getActive = function ()
{
if ( this._bindStack.length === 0 )
{
if ( this._bindBag.length === 0 )
{
if ( this._defaultRoot )
{
x3dom.debug.logInfo( "create new " + this._defaultType._typeName +
" for " + this._type._typeName + "-stack" );
var obj = new this._defaultType(
{ doc: this._doc, nameSpace: this._defaultRoot._nameSpace, autoGen: true } );
this._defaultRoot.addChild( obj );
obj.nodeChanged();
}
else
{
x3dom.debug.logError( "stack without defaultRoot" );
}
}
else
{
x3dom.debug.logInfo( "activate first " + this._type._typeName +
" for " + this._type._typeName + "-stack" );
}
this._bindStack.push( this._bindBag[ 0 ] );
this._bindBag[ 0 ].activate();
}
var active = this._bindStack[ this._bindStack.length - 1 ];
if ( active._nameSpace == null )
{
this.pop();
active = this.getActive();
}
return active;
};
/**
* BindableBag constructor
*
* @param doc
* @constructor
*/
x3dom.BindableBag = function ( doc )
{
this._stacks = [];
this.addType( "X3DViewpointNode", "Viewpoint", "getViewpoint", doc );
this.addType( "X3DNavigationInfoNode", "NavigationInfo", "getNavigationInfo", doc );
this.addType( "X3DBackgroundNode", "Background", "getBackground", doc );
this.addType( "X3DFogNode", "Fog", "getFog", doc );
this.addType( "X3DEnvironmentNode", "Environment", "getEnvironment", doc );
};
/**
* BindableBag Add Type
*
* @param typeName
* @param defaultTypeName
* @param getter
* @param doc
*/
x3dom.BindableBag.prototype.addType = function ( typeName, defaultTypeName, getter, doc )
{
var type = x3dom.nodeTypes[ typeName ];
var defaultType = x3dom.nodeTypes[ defaultTypeName ];
if ( type && defaultType )
{
var stack = new x3dom.BindableStack( doc, type, defaultType, getter );
this._stacks.push( stack );
}
else
{
x3dom.debug.logWarning( "Invalid Bindable type/defaultType: " +
typeName + "/" + defaultType );
}
};
/**
* BindableBag Set Ref Node
*
* @param node
*/
x3dom.BindableBag.prototype.setRefNode = function ( node )
{
this._stacks.forEach( function ( stack )
{
// set reference to Scene
stack._defaultRoot = node;
node[ stack._getter ] = function () { return stack.getActive(); };
} );
};
/**
* BindableBag Add Bindable
*
* @param node
* @returns {*}
*/
x3dom.BindableBag.prototype.addBindable = function ( node )
{
for ( var i = 0, n = this._stacks.length; i < n; i++ )
{
var stack = this._stacks[ i ];
if ( x3dom.isa( node, stack._type ) )
{
x3dom.debug.logInfo( "register " + node.typeName() + "Bindable " +
node._DEF + "/" + node._vf.description );
stack._bindBag.push( node );
var top = stack.top();
if ( top && top._autoGen )
{
stack.replaceTop( node );
// remove auto-generated default bindable
for ( var j = 0, m = stack._bindBag.length; j < m; j++ )
{
if ( stack._bindBag[ j ] === top )
{
stack._bindBag.splice( j, 1 );
break;
}
}
stack._defaultRoot.removeChild( top );
}
return stack;
}
}
x3dom.debug.logError( node.typeName() + " is not a valid bindable" );
return null;
};
/**
* X3DOM JavaScript Library
* http://www.x3dom.org
*
* (C)2009 Fraunhofer IGD, Darmstadt, Germany
* Dual licensed under the MIT and GPL
*
* Based on code originally provided by
* Philip Taylor: http://philip.html5.org
*/
/**
* NodeNameSpace constructor
*
* @param name
* @param document
* @constructor
*/
x3dom.NodeNameSpace = function ( name, document )
{
this.name = name;
this.doc = document;
this.baseURL = "";
this.setBaseURL( document.properties.properties.baseURL );
this.defMap = {};
this.parent = null;
this.childSpaces = [];
this.protos = []; // the ProtoDeclarationArray
this.lateRoutes = [];
this.imports = new Map(); //{inlineDEF => {AS => importedDEF}}
this.exports = new Map(); //{AS => localDEF}
this.superInlineNode = null;
};
/**
* NodeNameSpace Add Node
*
* @param node
* @param name
*/
x3dom.NodeNameSpace.prototype.addNode = function ( node, name )
{
this.defMap[ name ] = node;
//Imported nodes shoulden't be changed.
if ( !node._nameSpace )
{
node._nameSpace = this;
}
};
/**
* NodeNameSpace Remove Node
*
* @param name
*/
x3dom.NodeNameSpace.prototype.removeNode = function ( name )
{
var node = name ? this.defMap[ name ] : null;
if ( node )
{
delete this.defMap[ name ];
//Imported nodes shoulden't be changed.
if ( node._nameSpace == this )
{
node._nameSpace = null;
}
}
};
/**
* NodeNameSpace Get Named Node
*
* @param name
* @returns {*}
*/
x3dom.NodeNameSpace.prototype.getNamedNode = function ( name )
{
return this.defMap[ name ];
};
/**
* NodeNameSpace Get Named Element
*
* @param name
* @returns {null}
*/
x3dom.NodeNameSpace.prototype.getNamedElement = function ( name )
{
var node = this.defMap[ name ];
return ( node ? node._xmlNode : null );
};
/**
* NodeNameSpace Add Space
*
* @param space
*/
x3dom.NodeNameSpace.prototype.addSpace = function ( space )
{
this.childSpaces.push( space );
space.parent = this;
};
/**
* NodeNameSpace Remove Space
*
* @param space
*/
x3dom.NodeNameSpace.prototype.removeSpace = function ( space )
{
space.parent = null;
for ( var it = 0; it < this.childSpaces.length; it++ )
{
if ( this.childSpaces[ it ] == space )
{
this.childSpaces.splice( it, 1 );
}
}
};
/**
* NodeNameSpace Set Base URL
*
* @param url
*/
x3dom.NodeNameSpace.prototype.setBaseURL = function ( url )
{
var i = url.lastIndexOf( "/" );
this.baseURL = ( i >= 0 ) ? url.substr( 0, i + 1 ) : "";
x3dom.debug.logInfo( "setBaseURL: " + this.baseURL );
};
/**
* NodeNameSpace Get URL
*
* @param url
* @returns {*}
*/
x3dom.NodeNameSpace.prototype.getURL = function ( url )
{
if ( url === undefined || !url.length )
{
return "";
}
else
{
return ( ( url[ 0 ] === "/" ) || ( url.indexOf( ":" ) >= 0 ) ) ? url : ( this.baseURL + url );
}
};
/**
* Helper to check an element's attribute
*
* @param attrName
* @returns {*}
*/
x3dom.hasElementAttribute = function ( attrName )
{
var ok = this.__hasAttribute( attrName );
if ( !ok && attrName )
{
ok = this.__hasAttribute( attrName.toLowerCase() );
}
return ok;
};
/**
* Helper to get an element's attribute
*
* @param attrName
* @returns {*}
*/
x3dom.getElementAttribute = function ( attrName )
{
var attrib = this.__getAttribute( attrName );
if ( !attrib && attrib != "" && attrName )
{
attrib = this.__getAttribute( attrName.toLowerCase() );
}
if ( attrib != null || !this._x3domNode )
{
return attrib;
}
else
{
return this._x3domNode._vf[ attrName ];
}
};
/**
* Helper to set an element's attribute
*
* @param attrName
* @param newVal
*/
x3dom.setElementAttribute = function ( attrName, newVal )
{
//var prevVal = this.getAttribute(attrName);
this.__setAttribute( attrName, newVal );
//newVal = this.getAttribute(attrName);
var x3dNode = this._x3domNode;
if ( x3dNode )
{
if ( !x3dom.userAgentFeature.supportsMutationObserver )
{
x3dNode.updateField( attrName, newVal );
}
x3dNode._nameSpace.doc.needRender = true;
}
};
/**
* Returns the value of the field with the given name.
* The value is returned as an object of the corresponding field type.
*
* @param {String} fieldName - the name of the field
*/
x3dom.getFieldValue = function ( fieldName )
{
var x3dNode = this._x3domNode;
if ( x3dNode && ( x3dNode._vf[ fieldName ] !== undefined ) )
{
var fieldValue = x3dNode._vf[ fieldName ];
if ( fieldValue instanceof Object && "copy" in fieldValue )
{
return x3dNode._vf[ fieldName ].copy();
}
else
{
//f.i. SFString SFBool aren't objects
return x3dNode._vf[ fieldName ];
}
}
return null;
};
/**
* Sets the value of the field with the given name to the given value.
* The value is specified as an object of the corresponding field type.
*
* @param {String} fieldName - the name of the field where the value should be set
* @param {String} fieldvalue - the new field value
*/
x3dom.setFieldValue = function ( fieldName, fieldvalue )
{
var x3dNode = this._x3domNode;
if ( x3dNode && ( x3dNode._vf[ fieldName ] !== undefined ) && x3dNode._nameSpace )
{
// SF/MF object types are cloned based on a copy function
if ( fieldvalue instanceof Object && "copy" in fieldvalue )
{
x3dNode._vf[ fieldName ] = fieldvalue.copy();
}
else
{
//f.i. SFString SFBool aren't objects
x3dNode._vf[ fieldName ] = fieldvalue;
}
x3dNode.fieldChanged( fieldName );
x3dNode._nameSpace.doc.needRender = true;
}
};
/**
* Returns the field object of the field with the given name.
* The returned object is no copy, but instead a reference to X3DOM's internal field object.
* Changes to this object should be committed using the returnFieldRef function.
* Note: this only works for fields with pointer types such as MultiFields!
*
* @param {String} fieldName - the name of the field
*/
x3dom.requestFieldRef = function ( fieldName )
{
var x3dNode = this._x3domNode;
if ( x3dNode && x3dNode._vf[ fieldName ] )
{
return x3dNode._vf[ fieldName ];
}
return null;
};
/**
* Commits all changes made to the internal field object of the field with the given name.
* This must be done in order to notify X3DOM to process all related changes internally.
*
* @param {String} fieldName - the name of the field
*/
x3dom.releaseFieldRef = function ( fieldName )
{
var x3dNode = this._x3domNode;
if ( x3dNode && x3dNode._vf[ fieldName ] && x3dNode._nameSpace )
{
x3dNode.fieldChanged( fieldName );
x3dNode._nameSpace.doc.needRender = true;
}
};
/**
* attach X3DOM's custom field interface functions
* @param {Dom} domNode - the dom node to which functions are attached
*/
x3dom.attachFieldAccess = function ( domNode )
{
domNode.requestFieldRef = x3dom.requestFieldRef;
domNode.releaseFieldRef = x3dom.releaseFieldRef;
domNode.getFieldValue = x3dom.getFieldValue;
domNode.setFieldValue = x3dom.setFieldValue;
};
/**
* NodeNameSpace Setup Tree
*
* @param domNode
* @param parent
* @returns {*}
*/
x3dom.NodeNameSpace.prototype.setupTree = function ( domNode, parent )
{
var n = null;
parent = parent || null;
if ( x3dom.isX3DElement( domNode ) )
{
// return if it is already initialized
if ( domNode._x3domNode )
{
x3dom.debug.logWarning( "Tree is already initialized" );
return null;
}
// workaround since one cannot find out which handlers are registered
if ( ( domNode.tagName !== undefined ) &&
( !domNode.__addEventListener ) && ( !domNode.__removeEventListener ) )
{
// helper to track an element's listeners
domNode.__addEventListener = domNode.addEventListener;
domNode.addEventListener = function ( type, func, phase )
{
if ( !this._x3domNode._listeners[ type ] )
{
this._x3domNode._listeners[ type ] = [];
}
this._x3domNode._listeners[ type ].push( func );
//x3dom.debug.logInfo('addEventListener for ' + this.tagName + ".on" + type);
this.__addEventListener( type, func, phase );
};
domNode.__removeEventListener = domNode.removeEventListener;
domNode.removeEventListener = function ( type, func, phase )
{
var list = this._x3domNode._listeners[ type ];
if ( list )
{
for ( var it = 0; it < list.length; it++ )
{
if ( list[ it ] == func )
{
list.splice( it, 1 );
it--;
//x3dom.debug.logInfo('removeEventListener for ' +
// this.tagName + ".on" + type);
}
}
}
this.__removeEventListener( type, func, phase );
};
}
// TODO (?): dynamic update of USE attribute during runtime
if ( domNode.hasAttribute( "USE" ) || domNode.hasAttribute( "use" ) )
{
//fix usage of lowercase 'use'
if ( !domNode.hasAttribute( "USE" ) )
{
domNode.setAttribute( "USE", domNode.getAttribute( "use" ) );
}
n = this.defMap[ domNode.getAttribute( "USE" ) ];
if ( !n )
{
var nsName = domNode.getAttribute( "USE" ).split( "__" );
if ( nsName.length >= 2 )
{
var otherNS = this;
while ( otherNS )
{
if ( otherNS.name == nsName[ 0 ] )
{n = otherNS.defMap[ nsName[ 1 ] ];}
if ( n )
{
otherNS = null;
}
else
{
otherNS = otherNS.parent;
}
}
if ( !n )
{
n = null;
x3dom.debug.logWarning( "Could not USE: " + domNode.getAttribute( "USE" ) );
}
}
}
if ( n )
{
domNode._x3domNode = n;
x3dom.attachFieldAccess( domNode );
}
return n;
}
else
{
// check and create ROUTEs
if ( domNode.localName.toLowerCase() === "route" )
{
var route = domNode;
var fnDEF = route.getAttribute( "fromNode" ) || route.getAttribute( "fromnode" );
var tnDEF = route.getAttribute( "toNode" ) || route.getAttribute( "tonode" );
var fromNode = this.defMap[ fnDEF ];
var toNode = this.defMap[ tnDEF ];
var fnAtt = route.getAttribute( "fromField" ) || route.getAttribute( "fromfield" );
var tnAtt = route.getAttribute( "toField" ) || route.getAttribute( "tofield" );
if ( !( fromNode && toNode ) )
{
x3dom.debug.logWarning( "not yet available route - can't find all DEFs for " + fnAtt + " -> " + tnAtt );
this.lateRoutes.push( // save to check after protoextern instances loaded
{
route : route,
fnDEF : fnDEF,
tnDEF : tnDEF,
fnAtt : fnAtt,
tnAtt : tnAtt
} );
}
else
{
// x3dom.debug.logInfo( "ROUTE: from=" + fromNode._DEF + ", to=" + toNode._DEF );
fromNode.setupRoute( fnAtt, toNode, tnAtt );
// Store reference to namespace for being able to remove route later on
route._nodeNameSpace = this;
}
return null;
}
// check and create IMPORTs //Added by microaaron for IMPORT/EXPORT supported, 2021.11
if ( domNode.localName.toLowerCase() === "import" )
{
var inlineDEF = domNode.getAttribute( "inlineDEF" ) || domNode.getAttribute( "inlinedef" );
var importedDEF = domNode.getAttribute( "importedDEF" ) || domNode.getAttribute( "importeddef" );
var AS = domNode.getAttribute( "AS" ) || domNode.getAttribute( "as" );
if ( !inlineDEF || !importedDEF )
{
return null;
}
if ( !AS )
{
AS = importedDEF;
}
if ( !this.imports.get( inlineDEF ) )
{
this.imports.set( inlineDEF, new Map() );
}
this.imports.get( inlineDEF ).set( AS, importedDEF );
// Store reference to namespace for being able to remove import later on
domNode._nodeNameSpace = this;
if ( this.defMap[ inlineDEF ] && this.defMap[ inlineDEF ]._childNodes[ 0 ] && this.defMap[ inlineDEF ]._childNodes[ 0 ]._nameSpace )
{
var subNameSpace = this.defMap[ inlineDEF ]._childNodes[ 0 ]._nameSpace;
var localDEF = subNameSpace.exports.get( importedDEF );
if ( localDEF )
{
var importedNode = subNameSpace.defMap[ localDEF ];
if ( importedNode )
{
this.defMap[ AS ] = importedNode;
//x3dom.debug.logInfo( "Imported inlineDEF: " + inlineDEF + " importedDEF: " + importedDEF + " AS: " + AS);
this.routeLateRoutes();
}
}
}
return null;
}
// check and create EXPORTs //Added by microaaron for IMPORT/EXPORT supported, 2021.11
if ( domNode.localName.toLowerCase() === "export" )
{
var localDEF = domNode.getAttribute( "localDEF" ) || domNode.getAttribute( "localdef" );
var AS = domNode.getAttribute( "AS" ) || domNode.getAttribute( "as" );
if ( !localDEF )
{
return null;
}
if ( !AS )
{
AS = localDEF;
}
this.exports.set( AS, localDEF );
// Store reference to namespace for being able to remove export later on
domNode._nodeNameSpace = this;
if ( this.superInlineNode && this.superInlineNode._nameSpace )
{
this.superInlineNode._nameSpace.importNodes( this );
}
return null;
}
x3dom.attachFieldAccess( domNode );
// find the NodeType for the given dom-node
var nodeType = x3dom.nodeTypesLC[ domNode.localName.toLowerCase() ];
if ( nodeType === undefined )
{
x3dom.debug.logWarning( "Unrecognised X3D element &lt;" + domNode.localName + "&gt;." );
}
else
{
//active workaround for missing DOMAttrModified support
if ( ( x3dom.userAgentFeature.supportsDOMAttrModified === false )
&& ( domNode instanceof Element ) )
{
if ( domNode.setAttribute && !domNode.__setAttribute )
{
domNode.__setAttribute = domNode.setAttribute;
domNode.setAttribute = x3dom.setElementAttribute;
}
if ( domNode.getAttribute && !domNode.__getAttribute )
{
domNode.__getAttribute = domNode.getAttribute;
domNode.getAttribute = x3dom.getElementAttribute;
}
if ( domNode.hasAttribute && !domNode.__hasAttribute )
{
domNode.__hasAttribute = domNode.hasAttribute;
domNode.hasAttribute = x3dom.hasElementAttribute;
}
}
// create x3domNode
var ctx = {
doc : this.doc,
runtime : this.doc._x3dElem.runtime,
xmlNode : domNode,
nameSpace : this
};
n = new nodeType( ctx );
// find and store/link _DEF name
if ( domNode.hasAttribute( "DEF" ) )
{
n._DEF = domNode.getAttribute( "DEF" );
this.defMap[ n._DEF ] = n;
}
else
{
if ( domNode.hasAttribute( "id" ) )
{
n._DEF = domNode.getAttribute( "id" );
if ( !( n._DEF in this.defMap ) )
{
this.defMap[ n._DEF ] = n;
}
}
}
// add experimental highlighting functionality
if ( domNode.highlight === undefined )
{
domNode.highlight = function ( enable, colorStr )
{
var color = x3dom.fields.SFColor.parse( colorStr );
this._x3domNode.highlight( enable, color );
this._x3domNode._nameSpace.doc.needRender = true;
};
}
// link both DOM-Node and Scene-graph-Node
n._xmlNode = domNode;
domNode._x3domNode = n;
//register ProtoDeclares and convert ProtoInstance to new nodes
domNode.querySelectorAll( ":scope > *" )
. forEach( function ( childDomNode )
{
var tag = childDomNode.localName.toLowerCase();
if ( tag == "protodeclare" )
{ this.protoDeclare( childDomNode ); }
else if ( tag == "externprotodeclare" )
{ this.externProtoDeclare( childDomNode ); }
else if ( tag == "protoinstance" )
{ this.protoInstance( childDomNode, domNode ); }
}, this );
// call children
domNode.childNodes.forEach( function ( childDomNode )
{
var c = this.setupTree( childDomNode, n );
if ( c )
{
n.addChild( c, childDomNode.getAttribute( "containerField" ) );
}
}, this );
n.nodeChanged();
return n;
}
}
}
else if ( domNode.localName )
{
var tagLC = domNode.localName.toLowerCase();
//find not yet loaded externproto in case of direct syntax
var protoDeclaration = this.protos.find( function ( declaration )
{
return tagLC == declaration.name.toLowerCase() && declaration.isExternProto;
} );
if ( parent && tagLC == "x3dommetagroup" )
{
domNode.childNodes.forEach( function ( childDomNode )
{
var c = this.setupTree( childDomNode, parent );
if ( c )
{
parent.addChild( c, childDomNode.getAttribute( "containerField" ) );
}
}.bind( this ) );
}
//silence warnings
else if ( tagLC == "protodeclare" || tagLC == "externprotodeclare" || tagLC == "protoinstance" )
{
n = null;
}
else if ( domNode.localName.toLowerCase() == "is" )
{
if ( domNode.querySelectorAll( "connect" ).length == 0 )
{
x3dom.debug.logWarning( "IS statement without connect link: " + domNode.parentElement.localName );
}
}
//direct syntax
else if ( protoDeclaration )
{
this.loadExternProtoAsync( protoDeclaration, domNode, domNode, domNode.parentElement );
}
else
{
// be nice to users who use nodes not (yet) known to the system
x3dom.debug.logWarning( "Unrecognised X3D element &lt;" + domNode.localName + "&gt;." );
n = null;
}
}
return n;
};
/**
* NodeNameSpace importNodes
*
* @param subNameSpace
*
* Added by microaaron for IMPORT/EXPORT supported, 2021.11
*/
x3dom.NodeNameSpace.prototype.importNodes = function ( subNameSpace )
{
if ( !subNameSpace || !subNameSpace.superInlineNode || subNameSpace.superInlineNode._nameSpace != this )
{
return;
}
var that = this;
var inlineDEF = subNameSpace.superInlineNode._DEF;
var imports = that.imports;
var exports = subNameSpace.exports;
var counter = 0;
if ( inlineDEF && imports.get( inlineDEF ) )
{
imports.get( inlineDEF ).forEach( function ( importedDEF, AS )
{
var localDEF = exports.get( importedDEF );
if ( localDEF )
{
var importedNode = subNameSpace.defMap[ localDEF ];
if ( importedNode )
{
that.defMap[ AS ] = importedNode;
//x3dom.debug.logInfo( "Imported inlineDEF: " + inlineDEF + " importedDEF: " + importedDEF + " AS: " + AS);
counter++;
}
}
} );
if ( counter > 0 )
{
that.routeLateRoutes();
}
}
};
/**
* NodeNameSpace routeLateRoutes
*/
x3dom.NodeNameSpace.prototype.routeLateRoutes = function ()
{
var that = this;
that.lateRoutes.forEach( function ( route )
{
var fromNode = that.defMap[ route.fnDEF ];
var toNode = that.defMap[ route.tnDEF ];
if ( ( fromNode && toNode ) )
{
x3dom.debug.logInfo( "fixed ROUTE: from=" + fromNode._DEF + ", to=" + toNode._DEF );
fromNode.setupRoute( route.fnAtt, toNode, route.tnAtt );
route.route._nodeNameSpace = that;
}
} );
};
/**
* X3DOM JavaScript Library
* http://www.x3dom.org
*
* (C)2020 Andreas Plesch, Waltham, MA
* Dual licensed under the MIT and GPL
*
* Based on code originally provided by
* Philip Taylor: http://philip.html5.org
*/
/**
* NodeNameSpace protoInstance
*
* called from setupTree to process a ProtoInstance node. creates another dom node in short syntax.
* This short dom node is then processed back in setupTree. Additionally, it triggers loading an
* ExternProto declaration if required.
*
* @param domNode - the ProtoInstance dom node
* @param domParent - the parent dom node
*/
x3dom.NodeNameSpace.prototype.protoInstance = function ( domNode, domParent )
{
if ( !domNode.localName ) {return;}
if ( domNode._x3dom ) {return;}
var name = domNode.getAttribute( "name" );
//console.log( "found ProtoInstance", name, domNode );
//may be USE without name
if ( domNode.hasAttribute( "USE" ) )
{
var use = domNode.getAttribute( "USE" );
var defNode = this.defMap[ use ];
if ( defNode )
{
name = defNode.constructor._typeName;
}
else // try to find DEF in dom
{
name = domParent.getRootNode().querySelector( "[DEF=" + use + "]" ).getAttribute( "name" );
}
}
var protoDeclaration = this.protos.find( function ( proto ) { return proto.name == name; } );
if ( protoDeclaration == undefined )
{
x3dom.debug.logWarning( "ProtoInstance without a ProtoDeclaration " + name );
return;
}
//construct dom node
var protoInstanceDom = document.createElement( name );
//DEF/USE
if ( domNode.hasAttribute( "DEF" ) )
{
protoInstanceDom.setAttribute( "DEF", domNode.getAttribute( "DEF" ) );
}
else if ( domNode.hasAttribute( "USE" ) )
{
protoInstanceDom.setAttribute( "USE", domNode.getAttribute( "USE" ) );
}
if ( domNode.hasAttribute( "containerField" ) )
{
protoInstanceDom.setAttribute( "containerField", domNode.getAttribute( "containerField" ) );
}
//set fields to instance values
domNode.querySelectorAll( ":scope > fieldValue , :scope > fieldvalue" )
. forEach( function ( fieldValue )
{
var name = fieldValue.getAttribute( "name" );
var cfValue = fieldValue.querySelectorAll( ":scope > *" );
//check if Node value
if ( cfValue.length > 0 )
{
cfValue.forEach( function ( val )
{
val.setAttribute( "containerField", name );
protoInstanceDom.appendChild( val );
} );
}
else
{
var value = fieldValue.getAttribute( "value" );
if ( value )
{
protoInstanceDom.setAttribute( name, value );
}
}
} );
if ( protoDeclaration.isExternProto && protoDeclaration.needsLoading )
{
this.loadExternProtoAsync( protoDeclaration, protoInstanceDom, domNode, domParent );
return;
}
this.doc.mutationObserver.disconnect(); //avoid doubled processing
domNode.insertAdjacentElement( "afterend", protoInstanceDom ); // do not use appendChild since scene parent may be already transferred
var observedDOM = this.doc._x3dElem;
if ( this.doc._scene )
{
observedDOM = this.doc._scene._xmlNode;
}
this.doc.mutationObserver.observe( observedDOM, { attributes: true, attributeOldValue: true, childList: true, subtree: true } );
domNode._x3dom = protoInstanceDom;
};
/**
* NodeNameSpace loadExternProtoAsync
*
* called from protoInstance to load an extern protoDeclaration, and then instance the node.
*
* ExternProto declaration if required.
* @param protoDeclaration - the initial protoDeclaration stub, is replaced after loading
* @param protoInstanceDom - the short syntax proto instance dom node
* @param domNode - the regular syntax ProtoInstance dom node
* @param parentDom - the parent dom node
* @returns null - if downloading fails
*/
x3dom.NodeNameSpace.prototype.loadExternProtoAsync = function ( protoDeclaration, protoInstanceDom, domNode, parentDom )
{
//use queue to ensure processing in correct sequence
protoDeclaration.instanceQueue.push( {
"protoInstanceDom" : protoInstanceDom,
"domNode" : domNode,
"parentDom" : parentDom,
"targetChildIndex" : parentDom._x3domNode._childNodes.length + protoDeclaration.instanceQueue.length //since load is delayed
} );
domNode._x3dom = protoInstanceDom;
var that = this;
var urlIndex = 0;
function fetchExternProto ()
{
var url = that.getURL( protoDeclaration.url [ urlIndex ] );
that.doc.incrementDownloads();
fetch( url )
.then( function ( response )
{
if ( ! response.ok )
{
throw new Error( "Network response was not OK: " + response.status );
}
return response.text();
} )
.then( function ( text )
{
var parser = new DOMParser();
var doc = parser.parseFromString( text, "application/xml" );
var scene = doc.querySelector( "X3D" );
if ( scene == null )
{
doc = parser.parseFromString( text, "text/html" );
scene = doc.querySelector( "X3D" );
}
//find hash
var hash = url.includes( "#" ) ? url.split( "#" ).slice( -1 )[ 0 ] : "";
var selector = hash == "" ? "ProtoDeclare" : "ProtoDeclare[name='" + hash + "']";
var declareNode = scene.querySelector( selector );
//transfer name
declareNode.setAttribute( "name", protoDeclaration.name );
that.confNameSpace = new x3dom.NodeNameSpace( protoDeclaration.name, that.doc ); // instance name space
that.confNameSpace.setBaseURL( that.baseURL + protoDeclaration.name );
//that.addSpace( that.confNameSpace );
that.confNameSpace.setupTree( scene.querySelector( "Scene" ), scene );
//transfer proto definitions from other externprotos to namespace if any
that.confNameSpace.protos.forEach( function ( confProtoDeclaration )
{
that.protos.push( confProtoDeclaration );
} );
//remove current placeholder declaration
var currentIndex = that.protos.findIndex( function ( d )
{
return d == protoDeclaration;
} );
that.protos.splice( currentIndex, 1 );
that.protoDeclare( declareNode ); //add declaration as internal
//TODO check actual fields against fields in protoDeclaration
//warn if not matching but proceed ?
//that.protos[that.protos.length-1].fields ==? protoDeclaration.fields
//add instance(s) in order
var instance;
while ( instance = protoDeclaration.instanceQueue.shift() ) //process in correct sequence
{
var parentDom = instance.parentDom;
var parentNode = parentDom._x3domNode;
var instanceDom = instance.protoInstanceDom;
var targetIndex = instance.targetChildIndex;
that.doc.mutationObserver.disconnect();//do not record mutation
if ( instance.domNode !== instanceDom ) //check for short syntax
{
instance.domNode.insertAdjacentElement( "afterend", instanceDom ); // do not use appendChild since scene parent may be already transferred
}
that.doc.mutationObserver.observe( that.doc._scene._xmlNode, { attributes: true, attributeOldValue: true, childList: true, subtree: true } );
that.doc.onNodeAdded( instanceDom, parentDom );
var childNodes = parentNode._childNodes;
var childIndex = childNodes.length - 1;
if ( targetIndex !== childIndex )
{
//rearrange childNodes
var swapper = childNodes[ targetIndex ];
var instanceNode = childNodes[ childIndex ];
childNodes[ targetIndex ] = instanceNode;
childNodes[ childIndex ] = swapper;
//also containerfield array
var fieldName = parentDom.getAttribute( "containerField" );
var children = ( fieldName in parentNode._cf ) && parentNode._cf[ fieldName ];
if ( !children )
{
for ( var fieldName in parentNode._cf )
{
var field = parentNode._cf[ fieldName ];
if ( x3dom.isa( instanceNode, field.type ) )
{
children = field;
break;
}
}
}
children[ targetIndex ] = instanceNode;
children[ childIndex ] = swapper;
}
that.lateRoutes.forEach( function ( route )
{
var fromNode = that.defMap[ route.fnDEF ];
var toNode = that.defMap[ route.tnDEF ];
if ( ( fromNode && toNode ) )
{
x3dom.debug.logInfo( "fixed ROUTE: from=" + fromNode._DEF + ", to=" + toNode._DEF );
fromNode.setupRoute( route.fnAtt, toNode, route.tnAtt );
route.route._nodeNameSpace = that;
}
} );
if ( that.superInlineNode && that.superInlineNode._nameSpace )
{
that.superInlineNode._nameSpace.importNodes( that );
}
};
protoDeclaration.needsLoading = false;
that.doc.decrementDownloads();
} )
.catch( function ( error )
{
x3dom.debug.logWarning( url + ": ExternProto fetch failed: " + error );
that.doc.decrementDownloads();
urlIndex++;
if ( urlIndex < protoDeclaration.url.length )
{
fetchExternProto();
}
else
{
x3dom.debug.logError( "ExternProto fetch failed for all urls." );
protoDeclaration.needsLoading = false;
return null;
}
} );
};
fetchExternProto();
};
/**
* NodeNameSpace externProtoDeclare
*
* adds initial proto declaration to available prototype array
*
* @param domNode - the regular syntax ProtoInstance dom node
*/
x3dom.NodeNameSpace.prototype.externProtoDeclare = function ( domNode )
{
var name = domNode.getAttribute( "name" );
var url = x3dom.fields.MFString.parse( domNode.getAttribute( "url" ) );
//TODO parse fields to check later against actual fields in file
var fields = null;
var protoDeclaration = new x3dom.ProtoDeclaration( this, name, null, fields, true, url );
this.protos.push( protoDeclaration );
//protoinstance checks for name and triggers loading
};
/**
* NodeNameSpace protoDeclare
*
* processes ProtoDeclare node, called from setupTree.
* generates a new protoDeclaration, and then uses it to register the new node to x3dom
*
* @param domNode - the ProtoDeclaration dom node
*/
x3dom.NodeNameSpace.prototype.protoDeclare = function ( domNode )
{
var name = domNode.getAttribute( "name" );
var protoInterface = domNode.querySelector( "ProtoInterface" );
var fields = [];
if ( protoInterface )
{
var domFields = protoInterface.querySelectorAll( "field" );
domFields.forEach( function ( node )
{
fields.push( {
"name" : node.getAttribute( "name" ),
"accessType" : node.getAttribute( "accessType" ),
"dataType" : node.getAttribute( "type" ),
"value" : node.getAttribute( "value" ),
"cfValue" : node.querySelectorAll( ":scope > *" )
} );
} );
}
var protoBody = domNode.querySelector( "ProtoBody" );
if ( protoBody )
{
//find IS and make internal route template
protoBody._ISRoutes = {};
protoBody.querySelectorAll( "IS" ).forEach( function ( ISnode )
{
//check if inside another nested ProtoDeclare protobody
var parentBody = ISnode.parentElement;
while ( parentBody.localName.toLowerCase() !== "protobody" )
{
parentBody = parentBody.parentElement;
}
if ( parentBody !== protoBody ) {return;} // skip
ISnode.querySelectorAll( "connect" ).forEach( function ( connect )
{
var ISparent = ISnode.parentElement;
//assign unique DEF to parent if needed
if ( ISparent.hasAttribute( "DEF" ) == false )
{
var defname = "_proto_" +
ISparent.tagName + "_"
+ x3dom.protoISDEFuid++ ;
ISparent.setAttribute( "DEF", defname );
//add to defmap if protoinstance which has been already parsed
if ( ISparent.localName.toLowerCase() == "protoinstance" )
{
if ( ISparent._x3domNode )
{
ISparent._x3domNode._DEF = defname ;
ISparent._x3domNode.typeNode._nameSpace.defMap[ defname ] = ISparent._x3domNode ;
}
}
}
var protoField = connect.getAttribute( "protoField" );
var nodeDEF = ISparent.getAttribute( "DEF" );
var nodeField = connect.getAttribute( "nodeField" );
if ( !protoBody._ISRoutes[ protoField ] )
{
protoBody._ISRoutes[ protoField ] = [];
}
protoBody._ISRoutes[ protoField ].push( {
"nodeDEF" : nodeDEF,
"nodeField" : nodeField
} );
} );
} );
var protoDeclaration = new x3dom.ProtoDeclaration( this, name, protoBody, fields );
protoDeclaration.registerNode();
this.protos.push( protoDeclaration );
}
else
{
x3dom.debug.logWarning( "ProtoDeclare without a ProtoBody definition: " + domNode.name );
}
return "ProtoDeclare";
};
// uid for generated proto defs
x3dom.protoISDEFuid = 0;
/**
* X3DOM JavaScript Library
* http://www.x3dom.org
*
* (C)2009 Fraunhofer IGD, Darmstadt, Germany
* Dual licensed under the MIT and GPL
*
* Based on code originally provided by
* Philip Taylor: http://philip.html5.org
*/
x3dom.gfx_webgl = ( function ()
{
"use strict";
/**
* Context constructor
*
* @param ctx3d
* @param canvas
* @param name
* @param x3dElem
* @constructor
*/
function Context ( ctx3d, canvas, name, x3dElem )
{
this.ctx3d = ctx3d;
this.canvas = canvas;
this.name = name;
this.x3dElem = x3dElem;
this.IG_PositionBuffer = null;
this.cache = new x3dom.Cache();
this.stateManager = new x3dom.StateManager( ctx3d );
this.VRMode = 1;
this.BUFFER_IDX =
{
INDEX : 0,
POSITION : 1,
NORMAL : 2,
TEXCOORD : 3,
TEXCOORD_0 : 3,
COLOR : 4,
TANGENT : 6,
BITANGENT : 7,
TEXCOORD_1 : 8,
ID : 9
};
}
/**
* Return context name
*
* @returns {*}
*/
Context.prototype.getName = function ()
{
return this.name;
};
/**
* Setup the 3D context and init some things
*
* @param canvas
* @param x3dElem
*
* @returns {*}
*/
function setupContext ( canvas, x3dElem )
{
var validContextNames = [ "webgl2", "webgl", "experimental-webgl", "moz-webgl", "webkit-3d" ];
var isAppleDevice = ( /mac|ip(hone|od|ad)/i ).test( navigator.platform ),
isSafariBrowser = ( /safari/i ).test( navigator.userAgent ),
isIE11 = ( /trident\//i ).test( navigator.userAgent );
//Remove WebGL2 Support for Apple devices
if ( isAppleDevice )
{
validContextNames.splice( 0, 1 );
}
var ctx = null;
// FIXME: this is an ugly hack, don't look for elements like this
// (e.g., Bindable nodes may only exist in backend etc.)
var envNodes = x3dElem.getElementsByTagName( "Environment" );
var ssaoEnabled = ( envNodes && envNodes[ 0 ] && envNodes[ 0 ].hasAttribute( "SSAO" ) &&
envNodes[ 0 ].getAttribute( "SSAO" ).toLowerCase() === "true" ) ? true : false;
// Context creation params
var ctxAttribs = {
alpha : true,
depth : true,
stencil : true,
antialias : !ssaoEnabled,
premultipliedAlpha : false,
preserveDrawingBuffer : true,
failIfMajorPerformanceCaveat : true
};
for ( var i = 0; i < validContextNames.length; i++ )
{
try
{
x3dom.caps.RENDERMODE = "HARDWARE (" + validContextNames[ i ] + ")";
ctx = canvas.getContext( validContextNames[ i ], ctxAttribs );
// If context creation fails, retry the creation with failIfMajorPerformanceCaveat = false
if ( !ctx )
{
x3dom.caps.RENDERMODE = "SOFTWARE";
ctxAttribs.failIfMajorPerformanceCaveat = false;
ctx = canvas.getContext( validContextNames[ i ], ctxAttribs );
ctxAttribs.failIfMajorPerformanceCaveat = true;
}
if ( ctx )
{
var newCtx = new Context( ctx, canvas, "webgl", x3dElem );
try
{
// Save CAPS
x3dom.caps.VENDOR = ctx.getParameter( ctx.VENDOR );
x3dom.caps.VERSION = ctx.getParameter( ctx.VERSION );
x3dom.caps.WEBGL_VERSION = ( x3dom.caps.VERSION.indexOf( "WebGL 2.0" ) === -1 ) ? 1 : 2;
x3dom.caps.RENDERER = ctx.getParameter( ctx.RENDERER );
x3dom.caps.SHADING_LANGUAGE_VERSION = ctx.getParameter( ctx.SHADING_LANGUAGE_VERSION );
x3dom.caps.RED_BITS = ctx.getParameter( ctx.RED_BITS );
x3dom.caps.GREEN_BITS = ctx.getParameter( ctx.GREEN_BITS );
x3dom.caps.BLUE_BITS = ctx.getParameter( ctx.BLUE_BITS );
x3dom.caps.ALPHA_BITS = ctx.getParameter( ctx.ALPHA_BITS );
x3dom.caps.DEPTH_BITS = ctx.getParameter( ctx.DEPTH_BITS );
x3dom.caps.MAX_VERTEX_ATTRIBS = ctx.getParameter( ctx.MAX_VERTEX_ATTRIBS );
x3dom.caps.MAX_VERTEX_TEXTURE_IMAGE_UNITS = ctx.getParameter( ctx.MAX_VERTEX_TEXTURE_IMAGE_UNITS );
x3dom.caps.MAX_VARYING_VECTORS = ctx.getParameter( ctx.MAX_VARYING_VECTORS );
x3dom.caps.MAX_VERTEX_UNIFORM_VECTORS = ctx.getParameter( ctx.MAX_VERTEX_UNIFORM_VECTORS );
x3dom.caps.MAX_COMBINED_TEXTURE_IMAGE_UNITS = ctx.getParameter( ctx.MAX_COMBINED_TEXTURE_IMAGE_UNITS );
x3dom.caps.MAX_TEXTURE_SIZE = ctx.getParameter( ctx.MAX_TEXTURE_SIZE );
x3dom.caps.MAX_TEXTURE_IMAGE_UNITS = ctx.getParameter( ctx.MAX_TEXTURE_IMAGE_UNITS );
x3dom.caps.MAX_CUBE_MAP_TEXTURE_SIZE = ctx.getParameter( ctx.MAX_CUBE_MAP_TEXTURE_SIZE );
x3dom.caps.COMPRESSED_TEXTURE_FORMATS = ctx.getParameter( ctx.COMPRESSED_TEXTURE_FORMATS );
x3dom.caps.MAX_RENDERBUFFER_SIZE = ctx.getParameter( ctx.MAX_RENDERBUFFER_SIZE );
x3dom.caps.MAX_VIEWPORT_DIMS = ctx.getParameter( ctx.MAX_VIEWPORT_DIMS );
x3dom.caps.ALIASED_LINE_WIDTH_RANGE = ctx.getParameter( ctx.ALIASED_LINE_WIDTH_RANGE );
x3dom.caps.ALIASED_POINT_SIZE_RANGE = ctx.getParameter( ctx.ALIASED_POINT_SIZE_RANGE );
x3dom.caps.SAMPLES = ctx.getParameter( ctx.SAMPLES );
x3dom.caps.COMPRESSED_TEXTURE = ctx.getExtension( "WEBGL_compressed_texture_s3tc" );
x3dom.caps.INDEX_UINT = ctx.getExtension( "OES_element_index_uint" );
x3dom.caps.FP_TEXTURES = ctx.getExtension( "OES_texture_float" );
x3dom.caps.FPL_TEXTURES = ctx.getExtension( "OES_texture_float_linear" );
x3dom.caps.HFP_TEXTURES = ctx.getExtension( "OES_texture_half_float" );
x3dom.caps.COLOR_BUFFER_FLOAT = ctx.getExtension( "WEBGL_color_buffer_float" );
x3dom.caps.HFPL_TEXTURES = ctx.getExtension( "OES_texture_half_float_linear" );
x3dom.caps.STD_DERIVATIVES = ctx.getExtension( "OES_standard_derivatives" );
x3dom.caps.DRAW_BUFFERS = ctx.getExtension( "WEBGL_draw_buffers" );
x3dom.caps.DEPTH_TEXTURE = ctx.getExtension( "WEBGL_depth_texture" );
x3dom.caps.DEBUGRENDERINFO = ctx.getExtension( "WEBGL_debug_renderer_info" );
x3dom.caps.ANISOTROPIC = ctx.getExtension( "EXT_texture_filter_anisotropic" );
x3dom.caps.TEXTURE_LOD = ctx.getExtension( "EXT_shader_texture_lod" );
x3dom.caps.INSTANCED_ARRAYS = ctx.getExtension( "ANGLE_instanced_arrays" );
if ( x3dom.caps.ANISOTROPIC )
{
x3dom.caps.MAX_ANISOTROPY = ctx.getParameter( x3dom.caps.ANISOTROPIC.MAX_TEXTURE_MAX_ANISOTROPY_EXT );
}
x3dom.caps.EXTENSIONS = ctx.getSupportedExtensions();
// Enable/disable native webgl32 related caps
if ( x3dom.Utils.isWebGL2Enabled() )
{
x3dom.caps.DEPTH_TEXTURE = null;
x3dom.caps.INDEX_UINT = true;
}
if ( x3dom.caps.DEBUGRENDERINFO )
{
x3dom.caps.UNMASKED_RENDERER_WEBGL = ctx.getParameter( x3dom.caps.DEBUGRENDERINFO.UNMASKED_RENDERER_WEBGL );
x3dom.caps.UNMASKED_VENDOR_WEBGL = ctx.getParameter( x3dom.caps.DEBUGRENDERINFO.UNMASKED_VENDOR_WEBGL );
}
else
{
x3dom.caps.UNMASKED_RENDERER_WEBGL = "";
x3dom.caps.UNMASKED_VENDOR_WEBGL = "";
}
var extString = x3dom.caps.EXTENSIONS.toString().replace( /,/g, ", " );
x3dom.debug.logInfo( validContextNames[ i ] + " context found\nVendor: " + x3dom.caps.VENDOR +
" " + x3dom.caps.UNMASKED_VENDOR_WEBGL + ", Renderer: " + x3dom.caps.RENDERER +
" " + x3dom.caps.UNMASKED_RENDERER_WEBGL + ", " + "Version: " + x3dom.caps.VERSION + ", " +
"ShadingLangV.: " + x3dom.caps.SHADING_LANGUAGE_VERSION
+ ", MSAA samples: " + x3dom.caps.SAMPLES + "\nExtensions: " + extString );
if ( x3dom.caps.INDEX_UINT )
{
x3dom.Utils.maxIndexableCoords = 4294967295;
}
//Disable half float and float textures on apple devices
if ( isAppleDevice )
{
x3dom.caps.HFP_TEXTURES = false;
x3dom.caps.FP_TEXTURES = false;
}
//Disable texture lod on safari browsers
if ( isSafariBrowser )
{
x3dom.caps.TEXTURE_LOD = false;
}
//Disable instance arrays on safari browsers
if ( isIE11 )
{
x3dom.caps.INSTANCED_ARRAYS = false;
}
}
catch ( ex )
{
x3dom.debug.logWarning( "Your browser probably supports an older WebGL version." );
newCtx = null;
}
return newCtx;
}
}
catch ( e )
{
x3dom.debug.logWarning( e );
}
}
return null;
}
/**
* Setup GL objects for given shape
*
* @param gl
* @param drawable
* @param viewarea
*/
Context.prototype.setupShape = function ( gl, drawable, viewarea )
{
var q = 0,
q6,
textures,
t,
vertices,
positionBuffer,
texCoords,
texCoordBuffer,
tangents,
tangentBuffer,
binormals,
binormalBuffer,
indicesBuffer,
indexArray,
normals,
normalBuffer,
colors,
colorBuffer;
var shape = drawable.shape;
var geoNode = shape._cf.geometry.node;
if ( shape._webgl !== undefined )
{
var needFullReInit = false;
// TODO: do same for texcoords etc.!
if ( shape._dirty.colors === true &&
shape._webgl.shader.color === undefined && geoNode._mesh._colors[ 0 ].length )
{
// required since otherwise shape._webgl.shader.color stays undefined
// and thus the wrong shader will be chosen although there are colors
needFullReInit = true;
}
// cleanup vertex buffer objects
if ( needFullReInit && shape._cleanupGLObjects )
{
shape._cleanupGLObjects( true, false );
}
// Check for dirty Textures
if ( shape._dirty.texture === true )
{
// Check for Texture add or remove
if ( shape._webgl.texture.length != shape.getTextures().length )
{
// Delete old Textures
for ( t = 0; t < shape._webgl.texture.length; ++t )
{
shape._webgl.texture.pop();
}
// Generate new Textures
textures = shape.getTextures();
for ( t = 0; t < textures.length; ++t )
{
shape._webgl.texture.push( new x3dom.Texture( gl, shape._nameSpace.doc, this.cache, textures[ t ] ) );
}
// Set dirty shader
shape._dirty.shader = true;
// Set dirty texture Coordinates
if ( shape._webgl.shader.texcoord === undefined )
{shape._dirty.texcoords = true;}
}
else
{
// If someone remove and append at the same time, texture count don't change
// and we have to check if all nodes the same as before
textures = shape.getTextures();
for ( t = 0; t < textures.length; ++t )
{
if ( textures[ t ] === shape._webgl.texture[ t ].node )
{
// only update the texture
shape._webgl.texture[ t ].update();
}
else
{
// Set texture to null for recreation
shape._webgl.texture[ t ].texture = null;
// Set new node
shape._webgl.texture[ t ].node = textures[ t ];
// Update new node
shape._webgl.texture[ t ].update();
}
}
}
shape._dirty.texture = false;
}
// Check if we need a new shader
shape._webgl.shader = this.cache.getShaderByProperties( gl, shape, shape.getShaderProperties( viewarea ) );
if ( !needFullReInit && shape._webgl.binaryGeometry == 0 && shape._webgl.bufferGeometry == 0 )
{
// THINKABOUTME: What about PopGeo & Co.?
for ( q = 0; q < shape._webgl.positions.length; q++ )
{
q6 = 6 * q;
if ( shape._dirty.positions == true || shape._dirty.indexes == true )
{
if ( shape._webgl.shader.position !== undefined )
{
shape._webgl.indexes[ q ] = geoNode._mesh._indices[ q ];
gl.deleteBuffer( shape._webgl.buffers[ q6 + x3dom.BUFFER_IDX.INDEX ] );
indicesBuffer = gl.createBuffer();
shape._webgl.buffers[ q6 + x3dom.BUFFER_IDX.INDEX ] = indicesBuffer;
// explicitly check first positions array for consistency
if ( x3dom.caps.INDEX_UINT && ( geoNode._mesh._positions[ 0 ].length / 3 > 65535 ) )
{
indexArray = new Uint32Array( shape._webgl.indexes[ q ] );
shape._webgl.indexType = gl.UNSIGNED_INT;
}
else
{
indexArray = new Uint16Array( shape._webgl.indexes[ q ] );
shape._webgl.indexType = gl.UNSIGNED_SHORT;
}
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, indicesBuffer );
gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, indexArray, gl.STATIC_DRAW );
indexArray = null;
// vertex positions
shape._webgl.positions[ q ] = geoNode._mesh._positions[ q ];
// TODO: don't delete VBO but use glMapBuffer() and DYNAMIC_DRAW
gl.deleteBuffer( shape._webgl.buffers[ q6 + x3dom.BUFFER_IDX.POSITION ] );
positionBuffer = gl.createBuffer();
shape._webgl.buffers[ q6 + x3dom.BUFFER_IDX.POSITION ] = positionBuffer;
gl.bindBuffer( gl.ARRAY_BUFFER, positionBuffer );
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, shape._webgl.buffers[ q6 + x3dom.BUFFER_IDX.INDEX ] );
vertices = new Float32Array( shape._webgl.positions[ q ] );
gl.bufferData( gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW );
gl.bindBuffer( gl.ARRAY_BUFFER, positionBuffer );
gl.vertexAttribPointer( shape._webgl.shader.position,
geoNode._mesh._numPosComponents,
shape._webgl.coordType, false,
shape._coordStrideOffset[ 0 ], shape._coordStrideOffset[ 1 ] );
vertices = null;
}
shape._dirty.positions = false;
shape._dirty.indexes = false;
}
if ( shape._dirty.colors == true )
{
if ( shape._webgl.shader.color !== undefined )
{
shape._webgl.colors[ q ] = geoNode._mesh._colors[ q ];
gl.deleteBuffer( shape._webgl.buffers[ q6 + x3dom.BUFFER_IDX.COLOR ] );
colorBuffer = gl.createBuffer();
shape._webgl.buffers[ q6 + x3dom.BUFFER_IDX.COLOR ] = colorBuffer;
colors = new Float32Array( shape._webgl.colors[ q ] );
gl.bindBuffer( gl.ARRAY_BUFFER, colorBuffer );
gl.bufferData( gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW );
gl.vertexAttribPointer( shape._webgl.shader.color,
geoNode._mesh._numColComponents,
shape._webgl.colorType, false,
shape._colorStrideOffset[ 0 ], shape._colorStrideOffset[ 1 ] );
colors = null;
}
shape._dirty.colors = false;
}
if ( shape._dirty.normals == true )
{
if ( shape._webgl.shader.normal !== undefined )
{
shape._webgl.normals[ q ] = geoNode._mesh._normals[ q ];
gl.deleteBuffer( shape._webgl.buffers[ q6 + x3dom.BUFFER_IDX.NORMAL ] );
normalBuffer = gl.createBuffer();
shape._webgl.buffers[ q6 + x3dom.BUFFER_IDX.NORMAL ] = normalBuffer;
normals = new Float32Array( shape._webgl.normals[ q ] );
gl.bindBuffer( gl.ARRAY_BUFFER, normalBuffer );
gl.bufferData( gl.ARRAY_BUFFER, normals, gl.STATIC_DRAW );
gl.vertexAttribPointer( shape._webgl.shader.normal,
geoNode._mesh._numNormComponents,
shape._webgl.normalType, false,
shape._normalStrideOffset[ 0 ], shape._normalStrideOffset[ 1 ] );
normals = null;
}
shape._dirty.normals = false;
}
if ( shape._dirty.texcoords == true )
{
if ( shape._webgl.shader.texcoord !== undefined )
{
shape._webgl.texcoords[ q ] = geoNode._mesh._texCoords[ q ];
gl.deleteBuffer( shape._webgl.buffers[ q6 + x3dom.BUFFER_IDX.TEXCOORD ] );
texCoordBuffer = gl.createBuffer();
shape._webgl.buffers[ q6 + x3dom.BUFFER_IDX.TEXCOORD ] = texCoordBuffer;
texCoords = new Float32Array( shape._webgl.texcoords[ q ] );
gl.bindBuffer( gl.ARRAY_BUFFER, texCoordBuffer );
gl.bufferData( gl.ARRAY_BUFFER, texCoords, gl.STATIC_DRAW );
gl.vertexAttribPointer( shape._webgl.shader.texCoord,
geoNode._mesh._numTexComponents,
shape._webgl.texCoordType, false,
shape._texCoordStrideOffset[ 0 ], shape._texCoordStrideOffset[ 1 ] );
texCoords = null;
}
shape._dirty.texcoords = false;
}
if ( shape._dirty.specialAttribs == true )
{
if ( shape._webgl.shader.particleSize !== undefined )
{
var szArr = geoNode._vf.size.toGL();
if ( szArr.length )
{
gl.deleteBuffer( shape._webgl.buffers[ q6 + x3dom.BUFFER_IDX.ID ] );
shape._webgl.buffers[ q6 + x3dom.BUFFER_IDX.ID ] = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER, shape._webgl.buffers[ q6 + x3dom.BUFFER_IDX.ID ] );
gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( szArr ), gl.STATIC_DRAW );
}
shape._dirty.specialAttribs = false;
}
// Maybe other special attribs here, though e.g. AFAIK only BG (which not handled here) has ids.
}
}
}
else
{
// TODO: does not yet work with shared objects
/*
var spOld = shape._webgl.shader;
if (shape._cleanupGLObjects && needFullReInit)
shape._cleanupGLObjects(true, false);
// complete setup is sort of brute force, thus optimize!
x3dom.BinaryContainerLoader.setupBinGeo(shape, spOld, gl, viewarea, this);
shape.unsetGeoDirty();
*/
}
if ( !needFullReInit )
{
// we're done
return;
}
}
else if ( !( x3dom.isa( geoNode, x3dom.nodeTypes.Text ) ||
x3dom.isa( geoNode, x3dom.nodeTypes.BinaryGeometry ) ||
x3dom.isa( geoNode, x3dom.nodeTypes.PopGeometry ) ||
x3dom.isa( geoNode, x3dom.nodeTypes.BufferGeometry ) ) &&
( !geoNode || geoNode._mesh._positions[ 0 ].length < 1 ) )
{
x3dom.debug.logError( "NO VALID MESH OR NO VERTEX POSITIONS SET!" );
return;
}
// we're on init, thus reset all dirty flags
shape.unsetDirty();
// dynamically attach clean-up method for GL objects
if ( !shape._cleanupGLObjects )
{
shape._cleanupGLObjects = function cleanupGLObjects ( force, delGL )
{
// FIXME: what if complete tree is removed? Then _parentNodes.length may be greater 0.
if ( this._webgl && ( ( arguments.length > 0 && force ) || this._parentNodes.length == 0 ) )
{
var sp = this._webgl.shader;
for ( var q = 0; q < this._webgl.positions.length; q++ )
{
var q6 = 6 * q;
if ( sp.position !== undefined )
{
gl.deleteBuffer( this._webgl.buffers[ q6 + x3dom.BUFFER_IDX.INDEX ] );
gl.deleteBuffer( this._webgl.buffers[ q6 + x3dom.BUFFER_IDX.POSITION ] );
}
if ( sp.normal !== undefined )
{
gl.deleteBuffer( this._webgl.buffers[ q6 + x3dom.BUFFER_IDX.NORMAL ] );
}
if ( sp.texcoord !== undefined )
{
gl.deleteBuffer( this._webgl.buffers[ q6 + x3dom.BUFFER_IDX.TEXCOORD ] );
}
if ( sp.color !== undefined )
{
gl.deleteBuffer( this._webgl.buffers[ q6 + x3dom.BUFFER_IDX.COLOR ] );
}
if ( sp.id !== undefined )
{
gl.deleteBuffer( this._webgl.buffers[ q6 + x3dom.BUFFER_IDX.ID ] );
}
if ( sp.tangent !== undefined )
{
gl.deleteBuffer( this._webgl.buffers[ q6 + x3dom.BUFFER_IDX.TANGENT ] );
}
if ( sp.binormal !== undefined )
{
gl.deleteBuffer( this._webgl.buffers[ q6 + x3dom.BUFFER_IDX.BITANGENT ] );
}
}
for ( var df = 0; df < this._webgl.dynamicFields.length; df++ )
{
var attrib = this._webgl.dynamicFields[ df ];
if ( sp[ attrib.name ] !== undefined )
{
gl.deleteBuffer( attrib.buf );
}
}
if ( delGL === undefined )
{delGL = true;}
if ( delGL )
{
delete this._webgl;
// be optimistic, one shape removed makes room for another one
x3dom.BinaryContainerLoader.outOfMemory = false;
}
}
}; // shape._cleanupGLObjects()
}
shape._webgl = {
positions : geoNode._mesh._positions,
normals : geoNode._mesh._normals,
texcoords : geoNode._mesh._texCoords,
colors : geoNode._mesh._colors,
tangents : geoNode._mesh._tangents,
binormals : geoNode._mesh._binormals,
indexes : geoNode._mesh._indices,
// indicesBuffer,positionBuffer,normalBuffer,texcBuffer,colorBuffer
// buffers: [{},{},{},{},{}],
indexType : gl.UNSIGNED_SHORT,
coordType : gl.FLOAT,
normalType : gl.FLOAT,
texCoordType : gl.FLOAT,
texCoord2Type : gl.FLOAT,
colorType : gl.FLOAT,
tangentType : gl.FLOAT,
binormalType : gl.FLOAT,
coordNormalized : false,
normalNormalized : false,
texCoordNormalized : false,
texCoord2Normalized : false,
colorNormalized : false,
tangentNormalized : false,
binormalNormalized : false,
texture : [],
dirtyLighting : x3dom.Utils.checkDirtyLighting( viewarea ),
binaryGeometry : 0, // 0 := no BG, 1 := indexed BG, -1 := non-indexed BG
popGeometry : 0, // 0 : no PG, 1 : indexed PG, -1 : non-indexed PG
bufferGeometry : 0 // 0 : no EG, 1 : indexed EG, -1 : non-indexed EG
};
// Set Textures
textures = shape.getTextures();
for ( t = 0; t < textures.length; ++t )
{
shape._webgl.texture.push( new x3dom.Texture( gl, shape._nameSpace.doc, this.cache, textures[ t ] ) );
}
// Set Shader
// shape._webgl.shader = this.cache.getDynamicShader(gl, viewarea, shape);
// shape._webgl.shader = this.cache.getShaderByProperties(gl, drawable.properties);
shape._webgl.shader = this.cache.getShaderByProperties( gl, shape, shape.getShaderProperties( viewarea ) );
// init vertex attribs
var sp = shape._webgl.shader;
var currAttribs = 0;
shape._webgl.buffers = [];
shape._webgl.dynamicFields = [];
// Set Geometry Primitive Type
if ( x3dom.isa( geoNode, x3dom.nodeTypes.X3DBinaryContainerGeometryNode ) )
{
shape._webgl.primType = [];
for ( var primCnt = 0; primCnt < geoNode._vf.primType.length; ++primCnt )
{
shape._webgl.primType.push( x3dom.Utils.primTypeDic( gl, geoNode._vf.primType[ primCnt ] ) );
}
}
else
{
shape._webgl.primType = x3dom.Utils.primTypeDic( gl, geoNode._mesh._primType );
}
// Binary container geometries need special handling
if ( x3dom.isa( geoNode, x3dom.nodeTypes.BinaryGeometry ) )
{
x3dom.BinaryContainerLoader.setupBinGeo( shape, sp, gl, viewarea, this );
}
else if ( x3dom.isa( geoNode, x3dom.nodeTypes.BufferGeometry ) )
{
x3dom.BinaryContainerLoader.setupBufferGeo( shape, sp, gl, viewarea, this );
}
else if ( x3dom.isa( geoNode, x3dom.nodeTypes.PopGeometry ) )
{
x3dom.BinaryContainerLoader.setupPopGeo( shape, sp, gl, viewarea, this );
}
else
{
// No special BinaryMesh, but IFS or similar
for ( q = 0; q < shape._webgl.positions.length; q++ )
{
q6 = 6 * q;
if ( shape._webgl.positions[ q ] )
{
// bind indices for drawElements() call
indicesBuffer = gl.createBuffer();
shape._webgl.buffers[ q6 + x3dom.BUFFER_IDX.INDEX ] = indicesBuffer;
// explicitly check first positions array for consistency
if ( x3dom.caps.INDEX_UINT && ( shape._webgl.positions[ 0 ].length / 3 > 65535 ) )
{
indexArray = new Uint32Array( shape._webgl.indexes[ q ] );
shape._webgl.indexType = gl.UNSIGNED_INT;
}
else
{
indexArray = new Uint16Array( shape._webgl.indexes[ q ] );
shape._webgl.indexType = gl.UNSIGNED_SHORT;
}
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, indicesBuffer );
gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, indexArray, gl.STATIC_DRAW );
indexArray = null;
positionBuffer = gl.createBuffer();
shape._webgl.buffers[ q6 + x3dom.BUFFER_IDX.POSITION ] = positionBuffer;
gl.bindBuffer( gl.ARRAY_BUFFER, positionBuffer );
vertices = new Float32Array( shape._webgl.positions[ q ] );
gl.bufferData( gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW );
gl.bindBuffer( gl.ARRAY_BUFFER, positionBuffer );
gl.vertexAttribPointer( sp.position,
geoNode._mesh._numPosComponents,
shape._webgl.coordType, false,
shape._coordStrideOffset[ 0 ], shape._coordStrideOffset[ 1 ] );
gl.enableVertexAttribArray( sp.position );
vertices = null;
}
if ( shape._webgl.normals[ q ] )
{
normalBuffer = gl.createBuffer();
shape._webgl.buffers[ q6 + x3dom.BUFFER_IDX.NORMAL ] = normalBuffer;
var normals = new Float32Array( shape._webgl.normals[ q ] );
gl.bindBuffer( gl.ARRAY_BUFFER, normalBuffer );
gl.bufferData( gl.ARRAY_BUFFER, normals, gl.STATIC_DRAW );
gl.vertexAttribPointer( sp.normal,
geoNode._mesh._numNormComponents,
shape._webgl.normalType, false,
shape._normalStrideOffset[ 0 ], shape._normalStrideOffset[ 1 ] );
gl.enableVertexAttribArray( sp.normal );
normals = null;
}
if ( shape._webgl.texcoords[ q ] )
{
var texcBuffer = gl.createBuffer();
shape._webgl.buffers[ q6 + x3dom.BUFFER_IDX.TEXCOORD ] = texcBuffer;
var texCoords = new Float32Array( shape._webgl.texcoords[ q ] );
gl.bindBuffer( gl.ARRAY_BUFFER, texcBuffer );
gl.bufferData( gl.ARRAY_BUFFER, texCoords, gl.STATIC_DRAW );
gl.vertexAttribPointer( sp.texcoord,
geoNode._mesh._numTexComponents,
shape._webgl.texCoordType, false,
shape._texCoordStrideOffset[ 0 ], shape._texCoordStrideOffset[ 1 ] );
gl.enableVertexAttribArray( sp.texcoord );
texCoords = null;
}
if ( shape._webgl.colors[ q ] )
{
colorBuffer = gl.createBuffer();
shape._webgl.buffers[ q6 + x3dom.BUFFER_IDX.COLOR ] = colorBuffer;
var colors = new Float32Array( shape._webgl.colors[ q ] );
gl.bindBuffer( gl.ARRAY_BUFFER, colorBuffer );
gl.bufferData( gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW );
gl.vertexAttribPointer( sp.color,
geoNode._mesh._numColComponents,
shape._webgl.colorType, false,
shape._colorStrideOffset[ 0 ], shape._colorStrideOffset[ 1 ] );
gl.enableVertexAttribArray( sp.color );
colors = null;
}
if ( sp.particleSize !== undefined )
{
var sizeArr = geoNode._vf.size.toGL();
if ( sizeArr.length )
{
var sizeBuffer = gl.createBuffer();
shape._webgl.buffers[ q6 + x3dom.BUFFER_IDX.ID ] = sizeBuffer;
gl.bindBuffer( gl.ARRAY_BUFFER, sizeBuffer );
gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( sizeArr ), gl.STATIC_DRAW );
}
}
if ( shape._webgl.tangents[ q ] )
{
tangentBuffer = gl.createBuffer();
shape._webgl.buffers[ q6 + x3dom.BUFFER_IDX.TANGENT ] = tangentBuffer;
var tangents = new Float32Array( shape._webgl.tangents[ q ] );
gl.bindBuffer( gl.ARRAY_BUFFER, tangentBuffer );
gl.bufferData( gl.ARRAY_BUFFER, tangents, gl.STATIC_DRAW );
gl.vertexAttribPointer( sp.tangent,
geoNode._mesh._numTangentComponents,
shape._webgl.tangentType, false,
shape._tangentStrideOffset[ 0 ], shape._tangentStrideOffset[ 1 ] );
gl.enableVertexAttribArray( sp.tangent );
tangents = null;
}
if ( shape._webgl.binormals[ q ] )
{
binormalBuffer = gl.createBuffer();
shape._webgl.buffers[ q6 + x3dom.BUFFER_IDX.BITANGENT ] = binormalBuffer;
var binormals = new Float32Array( shape._webgl.binormals[ q ] );
gl.bindBuffer( gl.ARRAY_BUFFER, binormalBuffer );
gl.bufferData( gl.ARRAY_BUFFER, binormals, gl.STATIC_DRAW );
gl.vertexAttribPointer( sp.binormal,
geoNode._mesh._numBinormalComponents,
shape._webgl.binormalType, false,
shape._binormalStrideOffset[ 0 ], shape._binormalStrideOffset[ 1 ] );
gl.enableVertexAttribArray( sp.tangent );
binormals = null;
}
}
// FIXME: handle geometry with split mesh that has dynamic fields!
for ( var df in geoNode._mesh._dynamicFields )
{
if ( !geoNode._mesh._dynamicFields.hasOwnProperty( df ) )
{continue;}
var attrib = geoNode._mesh._dynamicFields[ df ];
shape._webgl.dynamicFields[ currAttribs ] = {
buf : {}, name : df, numComponents : attrib.numComponents
};
if ( sp[ df ] !== undefined )
{
var attribBuffer = gl.createBuffer();
shape._webgl.dynamicFields[ currAttribs++ ].buf = attribBuffer;
var attribs = new Float32Array( attrib.value );
gl.bindBuffer( gl.ARRAY_BUFFER, attribBuffer );
gl.bufferData( gl.ARRAY_BUFFER, attribs, gl.STATIC_DRAW );
gl.vertexAttribPointer( sp[ df ], attrib.numComponents, gl.FLOAT, false, 0, 0 );
attribs = null;
}
}
} // Standard geometry
};
/**
* Mainly manages rendering of backgrounds and buffer clearing
*
* @param gl
* @param bgnd
*/
Context.prototype.setupScene = function ( gl, bgnd )
{
var sphere = null;
var texture = null;
var that = this;
if ( bgnd._webgl !== undefined )
{
if ( !bgnd._dirty )
{
return;
}
if ( bgnd._webgl.texture !== undefined && bgnd._webgl.texture )
{
gl.deleteTexture( bgnd._webgl.texture );
}
if ( bgnd._cleanupGLObjects )
{
bgnd._cleanupGLObjects();
}
bgnd._webgl = {};
}
bgnd._dirty = false;
var url = bgnd.getTexUrl();
var i = 0;
var w = 1,
h = 1;
if ( url.length > 0 && url[ 0 ].length > 0 )
{
if ( ( url.length >= 6 && url[ 1 ].length > 0 && url[ 2 ].length > 0 &&
url[ 3 ].length > 0 && url[ 4 ].length > 0 && url[ 5 ].length > 0 ) ||
url[ 0 ].indexOf( ".dds" ) != -1 )
{
sphere = new x3dom.nodeTypes.Sphere();
bgnd._webgl = {
positions : sphere._mesh._positions[ 0 ],
indexes : sphere._mesh._indices[ 0 ],
buffers : [
{}, {}
]
};
bgnd._webgl.primType = gl.TRIANGLES;
if ( url[ 0 ].indexOf( ".dds" ) != -1 )
{
bgnd._webgl.shader = this.cache.getShader( gl, x3dom.shader.BACKGROUND_CUBETEXTURE_DDS );
bgnd._webgl.texture = x3dom.Utils.createTextureCube( gl, bgnd._nameSpace.doc, [ url[ 0 ] ],
false, bgnd._vf.crossOrigin, true, false, false );
}
else
{
bgnd._webgl.shader = this.cache.getShader( gl, x3dom.shader.BACKGROUND_CUBETEXTURE_DDS );
bgnd._webgl.texture = x3dom.Utils.createTextureCube( gl, bgnd._nameSpace.doc, url,
false, bgnd._vf.crossOrigin, true, false );
}
}
else
{
bgnd._webgl = {
positions : [ -w, -h, 0, -w, h, 0, w, -h, 0, w, h, 0 ],
indexes : [ 0, 1, 2, 3 ],
buffers : [
{}, {}
]
};
url = bgnd._nameSpace.getURL( url[ 0 ] );
bgnd._webgl.texture = x3dom.Utils.createTexture2D( gl, bgnd._nameSpace.doc, url,
true, bgnd._vf.crossOrigin, false, false );
bgnd._webgl.primType = gl.TRIANGLE_STRIP;
bgnd._webgl.shader = that.cache.getShader( gl, x3dom.shader.BACKGROUND_TEXTURE );
}
}
else
{
if ( bgnd.getSkyColor().length > 1 || bgnd.getGroundColor().length )
{
sphere = new x3dom.nodeTypes.Sphere();
texture = gl.createTexture();
bgnd._webgl = {
positions : sphere._mesh._positions[ 0 ],
texcoords : sphere._mesh._texCoords[ 0 ],
indexes : sphere._mesh._indices[ 0 ],
buffers : [
{}, {}, {}
],
texture : texture,
primType : gl.TRIANGLES
};
var N = x3dom.Utils.nextHighestPowerOfTwo(
bgnd.getSkyColor().length + bgnd.getGroundColor().length + 2 );
N = ( N < 512 ) ? 512 : N;
var n = bgnd._vf.groundAngle.length;
var tmp = [],
arr = [];
var colors = [],
sky = [ 0 ];
for ( i = 0; i < bgnd._vf.skyColor.length; i++ )
{
colors[ i ] = bgnd._vf.skyColor[ i ];
}
for ( i = 0; i < bgnd._vf.skyAngle.length; i++ )
{
sky[ i + 1 ] = bgnd._vf.skyAngle[ i ];
}
if ( n > 0 || bgnd._vf.groundColor.length == 1 )
{
if ( sky[ sky.length - 1 ] < Math.PI / 2 )
{
sky[ sky.length ] = Math.PI / 2 - x3dom.fields.Eps;
colors[ colors.length ] = colors[ colors.length - 1 ];
}
for ( i = n - 1; i >= 0; i-- )
{
if ( ( i == n - 1 ) && ( Math.PI - bgnd._vf.groundAngle[ i ] <= Math.PI / 2 ) )
{
sky[ sky.length ] = Math.PI / 2;
colors[ colors.length ] = bgnd._vf.groundColor[ bgnd._vf.groundColor.length - 1 ];
}
sky[ sky.length ] = Math.PI - bgnd._vf.groundAngle[ i ];
colors[ colors.length ] = bgnd._vf.groundColor[ i + 1 ];
}
if ( n == 0 && bgnd._vf.groundColor.length == 1 )
{
sky[ sky.length ] = Math.PI / 2;
colors[ colors.length ] = bgnd._vf.groundColor[ 0 ];
}
sky[ sky.length ] = Math.PI;
colors[ colors.length ] = bgnd._vf.groundColor[ 0 ];
}
else
{
if ( sky[ sky.length - 1 ] < Math.PI )
{
sky[ sky.length ] = Math.PI;
colors[ colors.length ] = colors[ colors.length - 1 ];
}
}
for ( i = 0; i < sky.length; i++ )
{
sky[ i ] /= Math.PI;
}
if ( sky.length != colors.length )
{
x3dom.debug.logError( "Number of background colors and corresponding angles do not match.\n"
+ "You have to define one angle less than the count of RGB colors because the angle 0° is added automatically." );
var minArrayLength = ( sky.length < colors.length ) ? sky.length : colors.length;
sky.length = minArrayLength;
colors.length = minArrayLength;
}
var interp = new x3dom.nodeTypes.ColorInterpolator();
interp._vf.key = new x3dom.fields.MFFloat( sky );
interp._vf.keyValue = new x3dom.fields.MFColor( colors );
interp._vf.RGB = true;
interp.fieldChanged( "keyValue" ); //update internals
for ( i = 0; i < N; i++ )
{
interp._vf.set_fraction = i / ( N - 1.0 );
interp.fieldChanged( "set_fraction" );
tmp[ i ] = interp._vf.value_changed;
}
tmp.reverse();
var alpha = Math.floor( ( 1.0 - bgnd.getTransparency() ) * 255 );
for ( i = 0; i < tmp.length; i++ )
{
arr.push( Math.floor( tmp[ i ].r * 255 ),
Math.floor( tmp[ i ].g * 255 ),
Math.floor( tmp[ i ].b * 255 ),
alpha );
}
var pixels = new Uint8Array( arr );
var format = gl.RGBA;
N = pixels.length / 4;
gl.bindTexture( gl.TEXTURE_2D, texture );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE );
gl.pixelStorei( gl.UNPACK_ALIGNMENT, 1 );
gl.texImage2D( gl.TEXTURE_2D, 0, format, 1, N, 0, format, gl.UNSIGNED_BYTE, pixels );
gl.bindTexture( gl.TEXTURE_2D, null );
bgnd._webgl.shader = that.cache.getShader( gl, x3dom.shader.BACKGROUND_SKYTEXTURE );
}
else
{
// Impl. gradient bg etc., e.g. via canvas 2d? But can be done via CSS anyway...
bgnd._webgl = {};
}
}
if ( bgnd._webgl.shader )
{
var sp = bgnd._webgl.shader;
var positionBuffer = gl.createBuffer();
bgnd._webgl.buffers[ x3dom.BUFFER_IDX.POSITION ] = positionBuffer;
gl.bindBuffer( gl.ARRAY_BUFFER, positionBuffer );
var vertices = new Float32Array( bgnd._webgl.positions );
gl.bufferData( gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW );
gl.bindBuffer( gl.ARRAY_BUFFER, positionBuffer );
gl.vertexAttribPointer( sp.position, 3, gl.FLOAT, false, 0, 0 );
gl.enableVertexAttribArray( sp.position );
var indicesBuffer = gl.createBuffer();
bgnd._webgl.buffers[ x3dom.BUFFER_IDX.INDEX ] = indicesBuffer;
var indexArray = new Uint16Array( bgnd._webgl.indexes );
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, indicesBuffer );
gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, indexArray, gl.STATIC_DRAW );
vertices = null;
indexArray = null;
if ( sp.texcoord !== undefined )
{
var texcBuffer = gl.createBuffer();
bgnd._webgl.buffers[ x3dom.BUFFER_IDX.TEXCOORD ] = texcBuffer;
var texcoords = new Float32Array( bgnd._webgl.texcoords );
gl.bindBuffer( gl.ARRAY_BUFFER, texcBuffer );
gl.bufferData( gl.ARRAY_BUFFER, texcoords, gl.STATIC_DRAW );
gl.vertexAttribPointer( sp.texcoord, 2, gl.FLOAT, false, 0, 0 );
gl.enableVertexAttribArray( sp.texcoord );
texcoords = null;
}
bgnd._cleanupGLObjects = function ()
{
var sp = this._webgl.shader;
if ( sp.position !== undefined )
{
gl.deleteBuffer( this._webgl.buffers[ x3dom.BUFFER_IDX.INDEX ] );
gl.deleteBuffer( this._webgl.buffers[ x3dom.BUFFER_IDX.POSITION ] );
}
if ( sp.texcoord !== undefined )
{
gl.deleteBuffer( this._webgl.buffers[ x3dom.BUFFER_IDX.TEXCOORD ] );
}
};
}
bgnd._webgl.render = function ( gl, mat_view, mat_proj, viewarea )
{
var sp = bgnd._webgl.shader;
var alpha = 1.0 - bgnd.getTransparency();
var mat_scene = null;
var projMatrix_22 = mat_proj._22,
projMatrix_23 = mat_proj._23;
var camPos = mat_view.e3();
if ( ( sp !== undefined && sp !== null ) &&
( sp.texcoord !== undefined && sp.texcoord !== null ) &&
( bgnd._webgl.texture !== undefined && bgnd._webgl.texture !== null ) )
{
gl.clearColor( 0, 0, 0, alpha );
gl.clearDepth( 1.0 );
gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT );
that.stateManager.frontFace( gl.CCW );
that.stateManager.disable( gl.CULL_FACE );
that.stateManager.disable( gl.DEPTH_TEST );
that.stateManager.disable( gl.BLEND );
that.stateManager.useProgram( sp );
if ( !sp.tex )
{
sp.tex = 0;
}
// adapt projection matrix to better near/far
mat_proj._22 = 100001 / 99999;
mat_proj._23 = 200000 / 99999;
// center viewpoint
mat_view._03 = 0;
mat_view._13 = 0;
mat_view._23 = 0;
mat_scene = mat_proj.mult( mat_view );
sp.modelViewProjectionMatrix = mat_scene.toGL();
mat_view._03 = camPos.x;
mat_view._13 = camPos.y;
mat_view._23 = camPos.z;
mat_proj._22 = projMatrix_22;
mat_proj._23 = projMatrix_23;
gl.activeTexture( gl.TEXTURE0 );
gl.bindTexture( gl.TEXTURE_2D, bgnd._webgl.texture );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE );
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, bgnd._webgl.buffers[ x3dom.BUFFER_IDX.INDEX ] );
gl.bindBuffer( gl.ARRAY_BUFFER, bgnd._webgl.buffers[ x3dom.BUFFER_IDX.POSITION ] );
gl.vertexAttribPointer( sp.position, 3, gl.FLOAT, false, 0, 0 );
gl.enableVertexAttribArray( sp.position );
gl.bindBuffer( gl.ARRAY_BUFFER, bgnd._webgl.buffers[ x3dom.BUFFER_IDX.TEXCOORD ] );
gl.vertexAttribPointer( sp.texcoord, 2, gl.FLOAT, false, 0, 0 );
gl.enableVertexAttribArray( sp.texcoord );
that.setVertexAttribEyeIdx( gl, sp );
that.drawElements( gl, bgnd._webgl.primType, bgnd._webgl.indexes.length, gl.UNSIGNED_SHORT, 0 );
gl.activeTexture( gl.TEXTURE0 );
gl.bindTexture( gl.TEXTURE_2D, null );
gl.disableVertexAttribArray( sp.position );
gl.disableVertexAttribArray( sp.texcoord );
that.disableVertexAttribEyeIdx( gl, sp );
gl.clear( gl.DEPTH_BUFFER_BIT );
}
else if ( !sp || !bgnd._webgl.texture ||
( bgnd._webgl.texture.textureCubeReady !== undefined &&
bgnd._webgl.texture.textureCubeReady !== true ) )
{
var bgCol = bgnd.getSkyColor().toGL();
gl.clearColor( bgCol[ 0 ], bgCol[ 1 ], bgCol[ 2 ], alpha );
gl.clearDepth( 1.0 );
gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT );
}
else
{
gl.clearColor( 0, 0, 0, alpha );
gl.clearDepth( 1.0 );
gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT );
that.stateManager.frontFace( gl.CCW );
that.stateManager.disable( gl.CULL_FACE );
that.stateManager.disable( gl.DEPTH_TEST );
that.stateManager.disable( gl.BLEND );
that.stateManager.useProgram( sp );
if ( !sp.tex )
{
sp.tex = 0;
}
if ( bgnd._webgl.texture.textureCubeReady )
{
// adapt projection matrix to better near/far
mat_proj._22 = 100001 / 99999;
mat_proj._23 = 200000 / 99999;
// center viewpoint
mat_view._03 = 0;
mat_view._13 = 0;
mat_view._23 = 0;
mat_scene = mat_proj.mult( mat_view );
sp.modelViewProjectionMatrix = mat_scene.toGL();
mat_view._03 = camPos.x;
mat_view._13 = camPos.y;
mat_view._23 = camPos.z;
mat_proj._22 = projMatrix_22;
mat_proj._23 = projMatrix_23;
gl.activeTexture( gl.TEXTURE0 );
gl.bindTexture( gl.TEXTURE_CUBE_MAP, bgnd._webgl.texture );
gl.texParameteri( gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR );
gl.texParameteri( gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR );
gl.texParameteri( gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE );
gl.texParameteri( gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE );
}
else
{
gl.activeTexture( gl.TEXTURE0 );
gl.bindTexture( gl.TEXTURE_2D, bgnd._webgl.texture );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE );
if ( bgnd._vf.scaling && bgnd._webgl.texture.ready )
{
var ratio = 1.0;
var viewport = new x3dom.fields.SFVec2f( that.canvas.width, that.canvas.height );
var texture = new x3dom.fields.SFVec2f( bgnd._webgl.texture.width, bgnd._webgl.texture.height );
if ( viewport.x > viewport.y )
{
ratio = viewport.x / texture.x;
texture.x = viewport.x;
texture.y = texture.y * ratio;
}
else
{
ratio = viewport.y / texture.y;
texture.y = viewport.y;
texture.x = texture.x * ratio;
}
var scale = viewport.divideComponents( texture );
var translation = texture.subtract( viewport ).multiply( 0.5 ).divideComponents( texture );
}
else
{
var scale = new x3dom.fields.SFVec2f( 1.0, 1.0 );
var translation = new x3dom.fields.SFVec2f( 0.0, 0.0 );
}
sp.scale = scale.toGL();
sp.translation = translation.toGL();
}
sp.isVR = -1.0;
sp.screenWidth = that.canvas.width;
that.setTonemappingOperator( viewarea, sp );
if ( that.VRMode == 2 )
{
var mat_view_R = viewarea.getViewMatrices()[ 1 ];
var camPosR = mat_view_R.e3();
mat_view_R._03 = 0;
mat_view_R._13 = 0;
mat_view_R._23 = 0;
var mat_proj_R = viewarea.getProjectionMatrices()[ 1 ];
var mat_scene_R = mat_proj_R.mult( mat_view_R );
sp.modelViewProjectionMatrix2 = mat_scene_R.toGL();
mat_view_R._03 = camPosR.x;
mat_view_R._13 = camPosR.y;
mat_view_R._23 = camPosR.z;
sp.isVR = 1.0;
}
that.setVertexAttribEyeIdx( gl, sp );
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, bgnd._webgl.buffers[ x3dom.BUFFER_IDX.INDEX ] );
gl.bindBuffer( gl.ARRAY_BUFFER, bgnd._webgl.buffers[ x3dom.BUFFER_IDX.POSITION ] );
gl.vertexAttribPointer( sp.position, 3, gl.FLOAT, false, 0, 0 );
gl.enableVertexAttribArray( sp.position );
that.drawElements( gl, bgnd._webgl.primType, bgnd._webgl.indexes.length, gl.UNSIGNED_SHORT, 0 );
gl.disableVertexAttribArray( sp.position );
that.disableVertexAttribEyeIdx( gl, sp );
gl.activeTexture( gl.TEXTURE0 );
if ( bgnd._webgl.texture.textureCubeReady )
{
gl.bindTexture( gl.TEXTURE_CUBE_MAP, null );
}
else
{
gl.bindTexture( gl.TEXTURE_2D, null );
}
gl.clear( gl.DEPTH_BUFFER_BIT );
}
};
};
/**
* Setup Frontgrounds
*
* @param gl
* @param scene
*/
Context.prototype.setupFgnds = function ( gl, scene )
{
if ( scene._fgnd !== undefined )
{
return;
}
var that = this;
var w = 1,
h = 1;
scene._fgnd = {};
scene._fgnd._webgl = {
positions : [ -w, -h, 0, -w, h, 0, w, -h, 0, w, h, 0 ],
indexes : [ 0, 1, 2, 3 ],
buffers : [
{}, {}
]
};
scene._fgnd._webgl.primType = gl.TRIANGLE_STRIP;
scene._fgnd._webgl.shader = this.cache.getShader( gl, x3dom.shader.FRONTGROUND_TEXTURE );
var sp = scene._fgnd._webgl.shader;
var positionBuffer = gl.createBuffer();
scene._fgnd._webgl.buffers[ x3dom.BUFFER_IDX.POSITION ] = positionBuffer;
gl.bindBuffer( gl.ARRAY_BUFFER, positionBuffer );
var vertices = new Float32Array( scene._fgnd._webgl.positions );
gl.bufferData( gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW );
gl.bindBuffer( gl.ARRAY_BUFFER, positionBuffer );
gl.vertexAttribPointer( sp.position, 3, gl.FLOAT, false, 0, 0 );
var indicesBuffer = gl.createBuffer();
scene._fgnd._webgl.buffers[ x3dom.BUFFER_IDX.INDEX ] = indicesBuffer;
var indexArray = new Uint16Array( scene._fgnd._webgl.indexes );
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, indicesBuffer );
gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, indexArray, gl.STATIC_DRAW );
vertices = null;
indexArray = null;
scene._fgnd._webgl.render = function ( gl, tex )
{
scene._fgnd._webgl.texture = tex;
that.stateManager.frontFace( gl.CCW );
that.stateManager.disable( gl.CULL_FACE );
that.stateManager.disable( gl.DEPTH_TEST );
that.stateManager.useProgram( sp );
if ( !sp.tex )
{
sp.tex = 0;
}
// this.stateManager.enable(gl.TEXTURE_2D);
gl.activeTexture( gl.TEXTURE0 );
gl.bindTexture( gl.TEXTURE_2D, scene._fgnd._webgl.texture );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE );
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, scene._fgnd._webgl.buffers[ x3dom.BUFFER_IDX.INDEX ] );
gl.bindBuffer( gl.ARRAY_BUFFER, scene._fgnd._webgl.buffers[ x3dom.BUFFER_IDX.POSITION ] );
gl.vertexAttribPointer( sp.position, 3, gl.FLOAT, false, 0, 0 );
gl.enableVertexAttribArray( sp.position );
that.drawElements( gl, scene._fgnd._webgl.primType, scene._fgnd._webgl.indexes.length, gl.UNSIGNED_SHORT, 0, 1 );
gl.disableVertexAttribArray( sp.position );
gl.activeTexture( gl.TEXTURE0 );
gl.bindTexture( gl.TEXTURE_2D, null );
// this.stateManager.disable(gl.TEXTURE_2D);
};
};
/**
* Render Shadow-Pass
*
* @param gl
* @param viewarea
* @param mat_scene
* @param mat_view
* @param targetFbo
* @param camOffset
* @param isCameraView
*/
Context.prototype.renderShadowPass = function ( gl, viewarea, mat_scene, mat_view, targetFbo, camOffset, isCameraView )
{
var scene = viewarea._scene;
var indicesReady = false;
this.stateManager.bindFramebuffer( gl.FRAMEBUFFER, targetFbo.fbo );
this.stateManager.viewport( 0, 0, targetFbo.width, targetFbo.height );
gl.clearColor( 1.0, 1.0, 1.0, 0.0 );
gl.clearDepth( 1.0 );
gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
this.stateManager.depthFunc( gl.LEQUAL );
this.stateManager.enable( gl.DEPTH_TEST );
this.stateManager.enable( gl.CULL_FACE );
this.stateManager.disable( gl.BLEND );
var bgCenter = x3dom.fields.SFVec3f.NullVector.toGL();
var bgSize = x3dom.fields.SFVec3f.OneVector.toGL();
var env = scene.getEnvironment();
var excludeTrans = env._vf.shadowExcludeTransparentObjects;
var i,
n = scene.drawableCollection.length;
for ( i = 0; i < n; i++ )
{
var drawable = scene.drawableCollection.get( i );
var trafo = drawable.transform;
var shape = drawable.shape;
var transparent = drawable.sortType == "transparent";
var s_gl = shape._webgl;
if ( !s_gl || ( excludeTrans && transparent ) )
{
continue;
}
var s_geo = shape._cf.geometry.node;
var s_app = shape._cf.appearance.node;
var s_msh = s_geo._mesh;
var properties = shape.getShaderProperties( viewarea );
// Generate Dynamic picking shader
var sp = this.cache.getShaderByProperties( gl, shape, properties, null, true );
if ( !sp )
{ // error
return;
}
// Bind shader
this.stateManager.useProgram( sp );
sp.cameraView = isCameraView;
sp.offset = camOffset;
sp.modelViewProjectionMatrix = mat_scene.mult( trafo ).toGL();
// BoundingBox stuff
if ( s_gl.coordType != gl.FLOAT )
{
if ( !s_gl.popGeometry && ( x3dom.Utils.isUnsignedType( s_geo._vf.coordType ) ) )
{
sp.bgCenter = s_geo.getMin().toGL();
}
else
{
sp.bgCenter = s_geo._vf.position.toGL();
}
sp.bgSize = s_geo._vf.size.toGL();
sp.bgPrecisionMax = s_geo.getPrecisionMax( "coordType" );
}
// Set ClipPlanes
if ( shape._clipPlanes )
{
sp.modelViewMatrix = mat_view.mult( trafo ).toGL();
sp.viewMatrixInverse = mat_view.inverse().toGL();
for ( var cp = 0; cp < shape._clipPlanes.length; cp++ )
{
var clip_plane = shape._clipPlanes[ cp ].plane;
var clip_trafo = shape._clipPlanes[ cp ].trafo;
sp[ "clipPlane" + cp + "_Plane" ] = clip_trafo.multMatrixPlane( clip_plane._vf.plane ).toGL();
sp[ "clipPlane" + cp + "_CappingStrength" ] = clip_plane._vf.cappingStrength;
sp[ "clipPlane" + cp + "_CappingColor" ] = clip_plane._vf.cappingColor.toGL();
}
}
if ( shape.isSolid() )
{
this.stateManager.enable( gl.CULL_FACE );
if ( shape.isCCW() )
{
this.stateManager.frontFace( gl.CCW );
}
else
{
this.stateManager.frontFace( gl.CW );
}
}
else
{
this.stateManager.disable( gl.CULL_FACE );
}
// Set DepthMode
var depthMode = s_app ? s_app._cf.depthMode.node : null;
var hasTexture = s_app ? s_app._cf.texture.node != null : false;
var writeDepth = !transparent || hasTexture;
if ( depthMode )
{
if ( depthMode._vf.enableDepthTest )
{
// Enable Depth Test
this.stateManager.enable( gl.DEPTH_TEST );
// Set Depth Mask
this.stateManager.depthMask( !depthMode._vf.readOnly );
// Set Depth Function
this.stateManager.depthFunc( x3dom.Utils.depthFunc( gl, depthMode._vf.depthFunc ) );
// Set Depth Range
this.stateManager.depthRange( depthMode._vf.zNearRange, depthMode._vf.zFarRange );
}
else
{
// Disable Depth Test
this.stateManager.disable( gl.DEPTH_TEST );
}
}
else
{
// Set Defaults
this.stateManager.enable( gl.DEPTH_TEST );
this.stateManager.depthMask( writeDepth );
this.stateManager.depthFunc( gl.LEQUAL );
}
// PopGeometry: adapt LOD and set shader variables
if ( s_gl.popGeometry )
{
var model_view = mat_view.mult( trafo );
// FIXME; viewarea's width/height twice as big as render buffer size, which leads to too high precision
// the correct viewarea here would be one that holds this half-sized render buffer
this.updatePopState( drawable, s_geo, sp, s_gl, scene, model_view, viewarea, this.x3dElem.runtime.fps );
}
var q_n;
q_n = s_gl.positions.length;
for ( var q = 0; q < q_n; q++ )
{
var q6 = 6 * q,
v,
v_n,
offset;
if ( !( sp.position !== undefined && s_gl.buffers[ q6 + x3dom.BUFFER_IDX.POSITION ] && s_gl.indexes[ q ] ) )
{continue;}
indicesReady = false;
// set buffers
if ( s_gl.buffers[ q6 + x3dom.BUFFER_IDX.INDEX ] )
{
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, s_gl.buffers[ q6 + x3dom.BUFFER_IDX.INDEX ] );
indicesReady = true;
}
this.setVertexAttribPointerPosition( gl, shape, q6, q );
if ( sp.id !== undefined && s_gl.buffers[ q6 + x3dom.BUFFER_IDX.ID ] )
{
gl.bindBuffer( gl.ARRAY_BUFFER, s_gl.buffers[ q6 + x3dom.BUFFER_IDX.ID ] );
// texture coordinate hack for IDs
if ( s_gl.binaryGeometry != 0 && s_geo._vf[ "idsPerVertex" ] == true )
{
gl.vertexAttribPointer( sp.id,
1, gl.FLOAT, false,
4, 0 );
gl.enableVertexAttribArray( sp.id );
}
}
// render mesh
if ( indicesReady && ( s_gl.binaryGeometry > 0 || s_gl.popGeometry > 0 ) )
{
for ( v = 0, offset = 0, v_n = s_geo._vf.vertexCount.length; v < v_n; v++ )
{
this.drawElements( gl, s_gl.primType[ v ], s_geo._vf.vertexCount[ v ], s_gl.indexType,
x3dom.Utils.getByteAwareOffset( offset, s_gl.indexType, gl ) );
offset += s_geo._vf.vertexCount[ v ];
}
}
else if ( s_gl.binaryGeometry < 0 || s_gl.popGeometry < 0 )
{
for ( v = 0, offset = 0, v_n = s_geo._vf.vertexCount.length; v < v_n; v++ )
{
this.drawArrays( gl, s_gl.primType[ v ], offset, s_geo._vf.vertexCount[ v ] );
offset += s_geo._vf.vertexCount[ v ];
}
}
else if ( indicesReady && ( s_gl.bufferGeometry > 0 ) )
{
this.drawElements( gl, s_gl.primType[ 0 ], s_geo._vf.vertexCount[ 0 ], s_gl.indexType, shape._indexOffset );
}
else if ( s_gl.bufferGeometry < 0 )
{
this.drawArrays( gl, s_gl.primType[ 0 ], 0, s_geo._vf.vertexCount[ 0 ] );
}
else if ( s_geo.hasIndexOffset() )
{
var indOff = shape.tessellationProperties();
for ( v = 0, v_n = indOff.length; v < v_n; v++ )
{
this.drawElements( gl, s_gl.primType, indOff[ v ].count, s_gl.indexType,
indOff[ v ].offset * x3dom.Utils.getOffsetMultiplier( s_gl.indexType, gl ) );
}
}
else if ( s_gl.indexes[ q ].length == 0 )
{
this.drawArrays( gl, s_gl.primType, 0, s_gl.positions[ q ].length / 3 );
}
else
{
this.drawElements( gl, s_gl.primType, s_gl.indexes[ q ].length, s_gl.indexType, 0 );
}
gl.disableVertexAttribArray( sp.position );
if ( sp.texcoord !== undefined && s_gl.buffers[ q6 + x3dom.BUFFER_IDX.TEXCOORD ] )
{
gl.disableVertexAttribArray( sp.texcoord );
}
if ( sp.color !== undefined && s_gl.buffers[ q6 + x3dom.BUFFER_IDX.COLOR ] )
{
gl.disableVertexAttribArray( sp.color );
}
if ( sp.id !== undefined && s_gl.buffers[ q6 + x3dom.BUFFER_IDX.ID ] )
{
gl.disableVertexAttribArray( sp.id );
}
}
}
if ( x3dom.Utils.needLineWidth )
{
this.stateManager.lineWidth( 1 );
}
this.stateManager.depthMask( true );
if ( depthMode )
{
this.stateManager.enable( gl.DEPTH_TEST );
this.stateManager.depthFunc( gl.LEQUAL );
this.stateManager.depthRange( 0, 1 );
}
gl.flush();
this.stateManager.bindFramebuffer( gl.FRAMEBUFFER, null );
};
/**
* Render Picking-Pass
*
* @param gl
* @param scene
* @param mat_view
* @param mat_scene
* @param from
* @param sceneSize
* @param pickMode
* @param lastX
* @param lastY
* @param width
* @param height
*/
Context.prototype.renderPickingPass = function ( gl, scene, mat_view, mat_scene, from, sceneSize,
pickMode, lastX, lastY, width, height )
{
var ps = scene._webgl.pickScale;
var bufHeight = scene._webgl.fboPick.height;
var x = lastX * ps;
var y = ( bufHeight - 1 ) - lastY * ps;
var indicesReady = false;
this.stateManager.bindFramebuffer( gl.FRAMEBUFFER, scene._webgl.fboPick.fbo );
this.stateManager.viewport( 0, 0, scene._webgl.fboPick.width, bufHeight );
// gl.scissor(x, y, width, height);
// gl.enable(gl.SCISSOR_TEST);
gl.clearColor( 0.0, 0.0, 0.0, 0.0 );
gl.clearDepth( 1.0 );
gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
var viewarea = scene.drawableCollection.viewarea;
var env = scene.getEnvironment();
var n = scene.drawableCollection.length;
if ( env._vf.smallFeatureCulling && env._lowPriorityThreshold < 1 && viewarea.isMovingOrAnimating() )
{
n = Math.floor( n * env._lowPriorityThreshold );
if ( !n && scene.drawableCollection.length )
{n = 1;} // render at least one object
}
var bgCenter = x3dom.fields.SFVec3f.NullVector.toGL();
var bgSize = x3dom.fields.SFVec3f.OneVector.toGL();
this.stateManager.depthFunc( gl.LEQUAL );
this.stateManager.enable( gl.DEPTH_TEST );
this.stateManager.enable( gl.CULL_FACE );
this.stateManager.disable( gl.BLEND );
if ( x3dom.Utils.needLineWidth )
{
this.stateManager.lineWidth( 2 ); // bigger lines for better picking
}
for ( var i = 0; i < n; i++ )
{
var drawable = scene.drawableCollection.get( i );
var trafo = drawable.transform;
var shape = drawable.shape;
var transparent = drawable.sortType == "transparent";
var s_gl = shape._webgl;
if ( !s_gl || shape._objectID < 1 || !shape._vf.isPickable )
{
continue;
}
var s_geo = shape._cf.geometry.node;
var s_app = shape._cf.appearance.node;
var s_msh = s_geo._mesh;
// Get shapes shader properties
var properties = shape.getShaderProperties( viewarea );
// Generate Dynamic picking shader
var sp = this.cache.getShaderByProperties( gl, shape, properties, pickMode );
if ( !sp )
{ // error
return;
}
// Save current shader
s_gl.shader = sp;
// Bind shader
this.stateManager.useProgram( sp );
sp.screenWidth = this.canvas.width * scene._webgl.pickScale;
sp.modelMatrix = trafo.toGL();
sp.modelViewProjectionMatrix = mat_scene.mult( trafo ).toGL();
sp.isVR = -1.0;
if ( this.VRMode == 2 )
{
var mat_view_R = viewarea.getViewMatrices()[ 1 ];
var mat_proj_R = viewarea.getProjectionMatrices()[ 1 ];
var mat_scene_R = mat_proj_R.mult( mat_view_R );
sp.modelViewProjectionMatrix2 = mat_scene_R.mult( trafo ).toGL();
sp.isVR = 1.0;
}
sp.lowBit = ( shape._objectID & 255 ) / 255.0;
sp.highBit = ( shape._objectID >>> 8 ) / 255.0;
sp.from = from.toGL();
sp.sceneSize = sceneSize;
// Set shadow ids if available
if ( s_gl.binaryGeometry != 0 && s_geo._vf[ "idsPerVertex" ] == true )
{
sp.shadowIDs = ( shape._vf.idOffset + x3dom.nodeTypes.Shape.objectID + 2 );
}
// BoundingBox stuff
if ( s_gl.coordType != gl.FLOAT )
{
if ( !s_gl.popGeometry && ( x3dom.Utils.isUnsignedType( s_geo._vf.coordType ) ) )
{
sp.bgCenter = s_geo.getMin().toGL();
}
else
{
sp.bgCenter = s_geo._vf.position.toGL();
}
sp.bgSize = s_geo._vf.size.toGL();
sp.bgPrecisionMax = s_geo.getPrecisionMax( "coordType" );
}
if ( pickMode == 1 && s_gl.colorType != gl.FLOAT )
{
sp.bgPrecisionColMax = s_geo.getPrecisionMax( "colorType" );
}
if ( pickMode == 2 && s_gl.texCoordType != gl.FLOAT )
{
sp.bgPrecisionTexMax = s_geo.getPrecisionMax( "texCoordType" );
}
// Set ClipPlanes
if ( shape._clipPlanes )
{
sp.modelViewMatrix = mat_view.mult( trafo ).toGL();
sp.viewMatrixInverse = mat_view.inverse().toGL();
if ( this.VRMode == 2 )
{
sp.modelViewMatrix2 = mat_view_R.mult( trafo ).toGL();
sp.viewMatrixInverse2 = mat_view_R.inverse().toGL();
}
for ( var cp = 0; cp < shape._clipPlanes.length; cp++ )
{
var clip_plane = shape._clipPlanes[ cp ].plane;
var clip_trafo = shape._clipPlanes[ cp ].trafo;
sp[ "clipPlane" + cp + "_Plane" ] = clip_trafo.multMatrixPlane( clip_plane._vf.plane ).toGL();
sp[ "clipPlane" + cp + "_CappingStrength" ] = clip_plane._vf.cappingStrength;
sp[ "clipPlane" + cp + "_CappingColor" ] = clip_plane._vf.cappingColor.toGL();
}
}
if ( shape.isSolid() )
{
this.stateManager.enable( gl.CULL_FACE );
if ( shape.isCCW() )
{
this.stateManager.frontFace( gl.CCW );
}
else
{
this.stateManager.frontFace( gl.CW );
}
}
else
{
this.stateManager.disable( gl.CULL_FACE );
}
// Set DepthMode
var depthMode = s_app ? s_app._cf.depthMode.node : null;
var hasTexture = s_app ? s_app._cf.texture.node != null : false;
var writeDepth = !transparent || hasTexture;
if ( depthMode )
{
if ( depthMode._vf.enableDepthTest )
{
// Enable Depth Test
this.stateManager.enable( gl.DEPTH_TEST );
// Set Depth Mask
this.stateManager.depthMask( !depthMode._vf.readOnly );
// Set Depth Function
this.stateManager.depthFunc( x3dom.Utils.depthFunc( gl, depthMode._vf.depthFunc ) );
// Set Depth Range
this.stateManager.depthRange( depthMode._vf.zNearRange, depthMode._vf.zFarRange );
}
else
{
// Disable Depth Test
this.stateManager.disable( gl.DEPTH_TEST );
}
}
else
{
// Set Defaults
this.stateManager.enable( gl.DEPTH_TEST );
this.stateManager.depthMask( writeDepth );
this.stateManager.depthFunc( gl.LEQUAL );
}
// PopGeometry: adapt LOD and set shader variables
if ( s_gl.popGeometry )
{
var model_view = mat_view.mult( trafo );
// FIXME; viewarea's width/height twice as big as render buffer size, which leads to too high precision
// the correct viewarea here would be one that holds this half-sized render buffer
this.updatePopState( drawable, s_geo, sp, s_gl, scene, model_view, viewarea, this.x3dElem.runtime.fps );
}
// TODO: ParticleSet for picking
var pointProperties = s_app ? s_app._cf.pointProperties.node : null;
pointProperties = pointProperties && (
x3dom.isa( s_geo, x3dom.nodeTypes.PointSet ) ||
x3dom.isa( s_geo, x3dom.nodeTypes.BinaryGeometry )
);
if ( pointProperties )
{
var pprop_vf = s_app._cf.pointProperties.node._vf;
sp.pointSizeAttenuation = pprop_vf.attenuation.toGL();
sp.pointSizeFactor = ps * pprop_vf.pointSizeScaleFactor; //scale by pickScale
sp.minPointSize = pprop_vf.pointSizeMinValue;
sp.maxPointSize = pprop_vf.pointSizeMaxValue;
// sp.modelViewMatrix = mat_view.mult( trafo ).toGL();
// sp.viewMatrixInverse = mat_view.inverse().toGL();
// if ( this.VRMode == 2 )
// {
// sp.modelViewMatrix2 = mat_view_R.mult( trafo ).toGL();
// sp.viewMatrixInverse2 = mat_view_R.inverse().toGL();
// }
}
var q_n = s_gl.positions.length;
for ( var q = 0; q < q_n; q++ )
{
var q6 = 6 * q;
var v,
v_n,
offset;
if ( !( sp.position !== undefined && s_gl.buffers[ q6 + x3dom.BUFFER_IDX.POSITION ] && s_gl.indexes[ q ] ) )
{continue;}
indicesReady = false;
// set buffers
if ( s_gl.buffers[ q6 + x3dom.BUFFER_IDX.INDEX ] )
{
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, s_gl.buffers[ q6 + x3dom.BUFFER_IDX.INDEX ] );
indicesReady = true;
}
this.setVertexAttribEyeIdx( gl, sp );
this.setVertexAttribPointerPosition( gl, shape, q6, q );
if ( pickMode == 1 )
{
this.setVertexAttribPointerColor( gl, shape, q6, q );
}
if ( pickMode == 2 && sp.texcoord !== undefined && s_gl.buffers[ q6 + x3dom.BUFFER_IDX.TEXCOORD ] )
{
this.setVertexAttribPointerTexCoord( gl, shape, q6, q );
}
if ( sp.id !== undefined && s_gl.buffers[ q6 + x3dom.BUFFER_IDX.ID ] )
{
gl.bindBuffer( gl.ARRAY_BUFFER, s_gl.buffers[ q6 + x3dom.BUFFER_IDX.ID ] );
// texture coordinate hack for IDs
if ( s_gl.binaryGeometry != 0 && s_geo._vf[ "idsPerVertex" ] == true )
{
gl.vertexAttribPointer( sp.id,
1, gl.FLOAT, false,
4, 0 );
gl.enableVertexAttribArray( sp.id );
}
}
// render mesh
if ( indicesReady && ( s_gl.binaryGeometry > 0 || s_gl.popGeometry > 0 ) )
{
for ( v = 0, offset = 0, v_n = s_geo._vf.vertexCount.length; v < v_n; v++ )
{
this.drawElements( gl, s_gl.primType[ v ], s_geo._vf.vertexCount[ v ], s_gl.indexType,
x3dom.Utils.getByteAwareOffset( offset, s_gl.indexType, gl ) );
offset += s_geo._vf.vertexCount[ v ];
}
}
else if ( s_gl.binaryGeometry < 0 || s_gl.popGeometry < 0 )
{
for ( v = 0, offset = 0, v_n = s_geo._vf.vertexCount.length; v < v_n; v++ )
{
this.drawArrays( gl, s_gl.primType[ v ], offset, s_geo._vf.vertexCount[ v ] );
offset += s_geo._vf.vertexCount[ v ];
}
}
else if ( indicesReady && ( s_gl.bufferGeometry > 0 ) )
{
this.drawElements( gl, s_gl.primType[ 0 ], s_geo._vf.vertexCount[ 0 ], s_gl.indexType, shape._indexOffset );
}
else if ( s_gl.bufferGeometry < 0 )
{
this.drawArrays( gl, s_gl.primType[ 0 ], 0, s_geo._vf.vertexCount[ 0 ] );
}
else if ( s_geo.hasIndexOffset() )
{
var indOff = shape.tessellationProperties();
for ( v = 0, v_n = indOff.length; v < v_n; v++ )
{
this.drawElements( gl, s_gl.primType, indOff[ v ].count, s_gl.indexType,
indOff[ v ].offset * x3dom.Utils.getOffsetMultiplier( s_gl.indexType, gl ) );
}
}
else if ( s_gl.indexes[ q ].length == 0 )
{
this.drawArrays( gl, s_gl.primType, 0, s_gl.positions[ q ].length / 3 );
}
else
{
this.drawElements( gl, s_gl.primType, s_gl.indexes[ q ].length, s_gl.indexType, 0 );
}
gl.disableVertexAttribArray( sp.position );
if ( sp.texcoord !== undefined && s_gl.buffers[ q6 + x3dom.BUFFER_IDX.TEXCOORD ] )
{
gl.disableVertexAttribArray( sp.texcoord );
}
if ( sp.color !== undefined && s_gl.buffers[ q6 + x3dom.BUFFER_IDX.COLOR ] )
{
gl.disableVertexAttribArray( sp.color );
}
if ( sp.id !== undefined && s_gl.buffers[ q6 + x3dom.BUFFER_IDX.ID ] )
{
gl.disableVertexAttribArray( sp.id );
}
this.disableVertexAttribEyeIdx( gl, sp );
}
}
if ( x3dom.Utils.needLineWidth )
{
this.stateManager.lineWidth( 1 );
}
this.stateManager.depthMask( true );
if ( depthMode )
{
this.stateManager.enable( gl.DEPTH_TEST );
this.stateManager.depthFunc( gl.LEQUAL );
this.stateManager.depthRange( 0, 1 );
}
gl.flush();
try
{
// 4 = 1 * 1 * 4; then take width x height window (exception pickRect)
var data = new Uint8Array( 4 * width * height );
gl.readPixels( x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, data );
scene._webgl.fboPick.pixelData = data;
}
catch ( se )
{
scene._webgl.fboPick.pixelData = [];
// No Exception on file:// when starting with additional flags:
// chrome.exe --disable-web-security
x3dom.debug.logException( se + " (cannot pick)" );
}
// gl.disable(gl.SCISSOR_TEST);
this.stateManager.bindFramebuffer( gl.FRAMEBUFFER, null );
};
/**
* Render single Shape
*
* @param drawable
* @param viewarea
* @param slights
* @param numLights
* @param mat_view
* @param mat_scene
* @param mat_light
* @param mat_proj
* @param gl
*/
Context.prototype.renderShape = function ( drawable, viewarea, slights, numLights, mat_view, mat_scene,
mat_light, mat_proj, gl )
{
// Variable to indicate that the indices are successful bind
var indicesReady = false;
var shape = drawable.shape;
var transform = drawable.transform;
var transparent = drawable.sortType == "transparent";
if ( !shape || !shape._webgl || !transform )
{
x3dom.debug.logError( "[Context|RenderShape] No valid Shape!" );
return;
}
var s_gl = shape._webgl;
var sp = s_gl.shader;
if ( !sp )
{
x3dom.debug.logError( "[Context|RenderShape] No Shader is set!" );
return;
}
var changed = this.stateManager.useProgram( sp );
// Set special Geometry variables
var s_app = shape._cf.appearance.node;
var s_geo = shape._cf.geometry.node;
var s_msh = s_geo._mesh;
var scene = viewarea._scene;
var tex = null;
if ( s_gl.coordType != gl.FLOAT )
{
if ( !s_gl.popGeometry && ( x3dom.Utils.isUnsignedType( s_geo._vf.coordType ) ) )
{
sp.bgCenter = s_geo.getMin().toGL();
}
else
{
sp.bgCenter = s_geo._vf.position.toGL();
}
sp.bgSize = s_geo._vf.size.toGL();
sp.bgPrecisionMax = s_geo.getPrecisionMax( "coordType" );
}
else
{
sp.bgCenter = [ 0, 0, 0 ];
sp.bgSize = [ 1, 1, 1 ];
sp.bgPrecisionMax = 1;
}
if ( s_gl.colorType != gl.FLOAT )
{
sp.bgPrecisionColMax = s_geo.getPrecisionMax( "colorType" );
}
else
{
sp.bgPrecisionColMax = 1;
}
if ( s_gl.texCoordType != gl.FLOAT )
{
sp.bgPrecisionTexMax = s_geo.getPrecisionMax( "texCoordType" );
}
else
{
sp.bgPrecisionTexMax = 1;
}
if ( s_gl.normalType != gl.FLOAT )
{
sp.bgPrecisionNorMax = s_geo.getPrecisionMax( "normalType" );
}
else
{
sp.bgPrecisionNorMax = 1;
}
if ( s_gl.tangentType != gl.FLOAT )
{
sp.bgPrecisionTangentMax = s_geo.getPrecisionMax( "tangentType" );
}
else
{
sp.bgPrecisionTangentMax = 1;
}
if ( s_gl.binormalType != gl.FLOAT )
{
sp.bgPrecisionBinormalMax = s_geo.getPrecisionMax( "binormalType" );
}
else
{
sp.bgPrecisionBinormalMax = 1;
}
// Set fog
// TODO: when no state/shader switch happens, all light/fog/... uniforms don't need to be set again
var fog = scene.getFog();
// THINKABOUTME: changed flag only works as long as lights and fog are global
if ( fog && changed )
{
sp.fogColor = fog._vf.color.toGL();
sp.fogRange = fog._vf.visibilityRange;
sp.fogType = ( fog._vf.fogType == "LINEAR" ) ? 0.0 : 1.0;
}
// Set Material
var mat = s_app ? s_app._cf.material.node : null;
var shader = s_app ? s_app._shader : null;
var twoSidedMat = false;
var isUserDefinedShader = shader && x3dom.isa( shader, x3dom.nodeTypes.ComposedShader );
if ( s_gl.csshader )
{
sp.diffuseColor = shader._vf.diffuseFactor.toGL();
sp.specularColor = shader._vf.specularFactor.toGL();
sp.emissiveColor = shader._vf.emissiveFactor.toGL();
sp.shininess = shader._vf.shininessFactor;
sp.ambientIntensity = ( shader._vf.ambientFactor.x +
shader._vf.ambientFactor.y +
shader._vf.ambientFactor.z ) / 3;
sp.transparency = 1.0 - shader._vf.alphaFactor;
sp.environmentFactor = shader._vf.environmentFactor.x;
sp.normalBias = shader._vf.normalBias.toGL();
if ( shader.getDisplacementMap() )
{
tex = x3dom.Utils.findTextureByName( s_gl.texture, "displacementMap" );
sp.displacementWidth = tex.texture.width;
sp.displacementHeight = tex.texture.height;
sp.displacementFactor = shader._vf.displacementFactor;
sp.displacementAxis = ( shader._vf.displacementAxis == "x" ) ? 0.0 :
( shader._vf.displacementAxis == "y" ) ? 1.0 : 2.0;
}
else if ( shader.getDiffuseDisplacementMap() )
{
tex = x3dom.Utils.findTextureByName( s_gl.texture, "diffuseDisplacementMap" );
sp.displacementWidth = tex.texture.width;
sp.displacementHeight = tex.texture.height;
sp.displacementFactor = shader._vf.displacementFactor;
sp.displacementAxis = ( shader._vf.displacementAxis == "x" ) ? 0.0 :
( shader._vf.displacementAxis == "y" ) ? 1.0 : 2.0;
}
}
else if ( mat && x3dom.isa( mat, x3dom.nodeTypes.PhysicalMaterial ) )
{
if ( mat._vf.model == "roughnessMetallic" )
{
sp.diffuseColor = [ mat._vf.baseColorFactor.r,
mat._vf.baseColorFactor.g,
mat._vf.baseColorFactor.b ];
sp.specularColor = [ x3dom.Utils.lerp( 0.04, mat._vf.baseColorFactor.r, mat._vf.metallicFactor ),
x3dom.Utils.lerp( 0.04, mat._vf.baseColorFactor.g, mat._vf.metallicFactor ),
x3dom.Utils.lerp( 0.04, mat._vf.baseColorFactor.b, mat._vf.metallicFactor ) ];
sp.shininess = 1.0 - mat._vf.roughnessFactor;
sp.metallicFactor = mat._vf.metallicFactor;
sp.transparency = 1.0 - mat._vf.baseColorFactor.a;
}
else
{
sp.diffuseColor = [ mat._vf.diffuseFactor.r,
mat._vf.diffuseFactor.g,
mat._vf.diffuseFactor.b ];
sp.specularColor = [ mat._vf.specularFactor.r,
mat._vf.specularFactor.g,
mat._vf.specularFactor.b ];
sp.shininess = mat._vf.glossinessFactor;
sp.transparency = 1.0 - mat._vf.diffuseFactor.a;
}
sp.emissiveColor = mat._vf.emissiveFactor.toGL();
sp.normalBias = mat._vf.normalBias.toGL();
sp.ambientIntensity = 1.0;
sp.alphaCutoff = mat._vf.alphaCutoff;
}
else if ( mat )
{
sp.diffuseColor = mat._vf.diffuseColor.toGL();
sp.specularColor = mat._vf.specularColor.toGL();
sp.emissiveColor = mat._vf.emissiveColor.toGL();
sp.shininess = mat._vf.shininess;
sp.ambientIntensity = mat._vf.ambientIntensity;
sp.transparency = mat._vf.transparency;
sp.environmentFactor = 0.0;
sp.alphaCutoff = s_app._vf.alphaClipThreshold.toFixed( 2 );
if ( x3dom.isa( mat, x3dom.nodeTypes.TwoSidedMaterial ) )
{
twoSidedMat = true;
sp.backDiffuseColor = mat._vf.backDiffuseColor.toGL();
sp.backSpecularColor = mat._vf.backSpecularColor.toGL();
sp.backEmissiveColor = mat._vf.backEmissiveColor.toGL();
sp.backShininess = mat._vf.backShininess;
sp.backAmbientIntensity = mat._vf.backAmbientIntensity;
sp.backTransparency = mat._vf.backTransparency;
}
}
else
{
sp.diffuseColor = [ 1.0, 1.0, 1.0 ];
sp.specularColor = [ 0.0, 0.0, 0.0 ];
sp.emissiveColor = [ 0.0, 0.0, 0.0 ];
sp.shininess = 0.0;
sp.ambientIntensity = 1.0;
sp.transparency = 0.0;
sp.alphaCutoff = 0.1;
}
// Look for user-defined shaders
if ( shader )
{
if ( isUserDefinedShader )
{
for ( var fName in shader._vf )
{
if ( shader._vf.hasOwnProperty( fName ) && fName !== "language" )
{
var field = shader._vf[ fName ];
if ( field !== undefined && field !== null )
{
if ( field.toGL )
{
sp[ fName ] = field.toGL();
}
else
{
sp[ fName ] = field;
}
}
}
}
}
else if ( x3dom.isa( shader, x3dom.nodeTypes.CommonSurfaceShader ) )
{
s_gl.csshader = shader;
}
}
// Set Lights
//===========================================================================
var physicalEnvironmentLight;
for ( var p = 0; p < numLights && changed; p++ )
{
// FIXME; getCurrentTransform() doesn't work for shared lights/objects!
var light_transform = mat_view.mult( slights[ p ].getCurrentTransform() );
if ( x3dom.isa( slights[ p ], x3dom.nodeTypes.DirectionalLight ) )
{
sp[ "light" + p + "_Type" ] = 0.0;
sp[ "light" + p + "_On" ] = ( slights[ p ]._vf.on ) ? 1.0 : 0.0;
sp[ "light" + p + "_Color" ] = slights[ p ]._vf.color.toGL();
sp[ "light" + p + "_Intensity" ] = slights[ p ]._vf.intensity;
sp[ "light" + p + "_AmbientIntensity" ] = slights[ p ]._vf.ambientIntensity;
sp[ "light" + p + "_Direction" ] = light_transform.multMatrixVec( slights[ p ]._vf.direction ).toGL();
sp[ "light" + p + "_Attenuation" ] = [ 1.0, 1.0, 1.0 ];
sp[ "light" + p + "_Location" ] = [ 1.0, 1.0, 1.0 ];
sp[ "light" + p + "_Radius" ] = 0.0;
sp[ "light" + p + "_BeamWidth" ] = 0.0;
sp[ "light" + p + "_CutOffAngle" ] = 0.0;
sp[ "light" + p + "_ShadowIntensity" ] = slights[ p ]._vf.shadowIntensity;
}
else if ( x3dom.isa( slights[ p ], x3dom.nodeTypes.PointLight ) )
{
sp[ "light" + p + "_Type" ] = 1.0;
sp[ "light" + p + "_On" ] = ( slights[ p ]._vf.on ) ? 1.0 : 0.0;
sp[ "light" + p + "_Color" ] = slights[ p ]._vf.color.toGL();
sp[ "light" + p + "_Intensity" ] = slights[ p ]._vf.intensity;
sp[ "light" + p + "_AmbientIntensity" ] = slights[ p ]._vf.ambientIntensity;
sp[ "light" + p + "_Direction" ] = [ 1.0, 1.0, 1.0 ];
sp[ "light" + p + "_Attenuation" ] = slights[ p ]._vf.attenuation.toGL();
sp[ "light" + p + "_Location" ] = light_transform.multMatrixPnt( slights[ p ]._vf.location ).toGL();
sp[ "light" + p + "_Radius" ] = slights[ p ]._vf.radius;
sp[ "light" + p + "_BeamWidth" ] = 0.0;
sp[ "light" + p + "_CutOffAngle" ] = 0.0;
sp[ "light" + p + "_ShadowIntensity" ] = slights[ p ]._vf.shadowIntensity;
}
else if ( x3dom.isa( slights[ p ], x3dom.nodeTypes.SpotLight ) )
{
sp[ "light" + p + "_Type" ] = 2.0;
sp[ "light" + p + "_On" ] = ( slights[ p ]._vf.on ) ? 1.0 : 0.0;
sp[ "light" + p + "_Color" ] = slights[ p ]._vf.color.toGL();
sp[ "light" + p + "_Intensity" ] = slights[ p ]._vf.intensity;
sp[ "light" + p + "_AmbientIntensity" ] = slights[ p ]._vf.ambientIntensity;
sp[ "light" + p + "_Direction" ] = light_transform.multMatrixVec( slights[ p ]._vf.direction ).toGL();
sp[ "light" + p + "_Attenuation" ] = slights[ p ]._vf.attenuation.toGL();
sp[ "light" + p + "_Location" ] = light_transform.multMatrixPnt( slights[ p ]._vf.location ).toGL();
sp[ "light" + p + "_Radius" ] = slights[ p ]._vf.radius;
sp[ "light" + p + "_BeamWidth" ] = slights[ p ]._vf.beamWidth;
sp[ "light" + p + "_CutOffAngle" ] = slights[ p ]._vf.cutOffAngle;
sp[ "light" + p + "_ShadowIntensity" ] = slights[ p ]._vf.shadowIntensity;
}
else if ( x3dom.isa( slights[ p ], x3dom.nodeTypes.PhysicalEnvironmentLight ) )
{
physicalEnvironmentLight = slights[ p ];
numLights--;
}
}
// Set HeadLight
var nav = scene.getNavigationInfo();
if ( nav._vf.headlight && changed )
{
numLights = ( numLights ) ? numLights : 0;
sp[ "light" + numLights + "_Type" ] = 0.0;
sp[ "light" + numLights + "_On" ] = 1.0;
sp[ "light" + numLights + "_Color" ] = [ 1.0, 1.0, 1.0 ];
sp[ "light" + numLights + "_Intensity" ] = 1.0;
sp[ "light" + numLights + "_AmbientIntensity" ] = 0.0;
sp[ "light" + numLights + "_Direction" ] = [ 0.0, 0.0, -1.0 ];
sp[ "light" + numLights + "_Attenuation" ] = [ 1.0, 1.0, 1.0 ];
sp[ "light" + numLights + "_Location" ] = [ 1.0, 1.0, 1.0 ];
sp[ "light" + numLights + "_Radius" ] = 0.0;
sp[ "light" + numLights + "_BeamWidth" ] = 0.0;
sp[ "light" + numLights + "_CutOffAngle" ] = 0.0;
sp[ "light" + numLights + "_ShadowIntensity" ] = 0.0;
}
// Set ClipPlanes
if ( shape._clipPlanes )
{
for ( var cp = 0; cp < shape._clipPlanes.length; cp++ )
{
var clip_plane = shape._clipPlanes[ cp ].plane;
var clip_trafo = shape._clipPlanes[ cp ].trafo;
sp[ "clipPlane" + cp + "_Plane" ] = clip_trafo.multMatrixPlane( clip_plane._vf.plane ).toGL();
sp[ "clipPlane" + cp + "_CappingStrength" ] = clip_plane._vf.cappingStrength;
sp[ "clipPlane" + cp + "_CappingColor" ] = clip_plane._vf.cappingColor.toGL();
}
}
// Set DepthMode
var depthMode = s_app ? s_app._cf.depthMode.node : null;
var hasTexture = s_app ? s_app._cf.texture.node != null : false;
var writeDepth = !transparent || hasTexture;
if ( depthMode )
{
if ( depthMode._vf.enableDepthTest )
{
// Enable Depth Test
this.stateManager.enable( gl.DEPTH_TEST );
// Set Depth Mask
this.stateManager.depthMask( !depthMode._vf.readOnly );
// Set Depth Function
this.stateManager.depthFunc( x3dom.Utils.depthFunc( gl, depthMode._vf.depthFunc ) );
// Set Depth Range
this.stateManager.depthRange( depthMode._vf.zNearRange, depthMode._vf.zFarRange );
}
else
{
// Disable Depth Test
this.stateManager.disable( gl.DEPTH_TEST );
}
}
else
{
// Set Defaults
this.stateManager.enable( gl.DEPTH_TEST );
this.stateManager.depthMask( writeDepth );
this.stateManager.depthFunc( gl.LEQUAL );
}
// Set BlendMode
var blendMode = s_app ? s_app._cf.blendMode.node : null;
if ( blendMode )
{
var srcFactor = x3dom.Utils.blendFunc( gl, blendMode._vf.srcFactor );
var destFactor = x3dom.Utils.blendFunc( gl, blendMode._vf.destFactor );
if ( srcFactor !== false && destFactor !== false )
{
// Enable Blending
this.stateManager.enable( gl.BLEND );
// Set Blend Function
this.stateManager.blendFuncSeparate( srcFactor, destFactor, gl.ONE, gl.ONE );
// Set Blend Color
this.stateManager.blendColor( blendMode._vf.color.r,
blendMode._vf.color.g,
blendMode._vf.color.b,
1.0 - blendMode._vf.colorTransparency );
// Set Blend Equation
this.stateManager.blendEquation( x3dom.Utils.blendEquation( gl, blendMode._vf.equation ) );
}
else
{
this.stateManager.disable( gl.BLEND );
}
}
else
{
//Get it from physicalmaterial or set defaults
if ( mat && x3dom.isa( mat, x3dom.nodeTypes.PhysicalMaterial ) )
{
if ( mat._vf.alphaMode == "BLEND" )
{
this.stateManager.enable( gl.BLEND );
this.stateManager.blendFuncSeparate( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE );
}
else
{
this.stateManager.disable( gl.BLEND );
}
}
else
{
// Set Defaults
this.stateManager.enable( gl.BLEND );
this.stateManager.blendFuncSeparate( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE );
}
}
// Set ColorMaskMode
var colorMaskMode = s_app ? s_app._cf.colorMaskMode.node : null;
if ( colorMaskMode )
{
this.stateManager.colorMask( colorMaskMode._vf.maskR,
colorMaskMode._vf.maskG,
colorMaskMode._vf.maskB,
colorMaskMode._vf.maskA );
}
else
{
// Set Defaults
this.stateManager.colorMask( true, true, true, true );
}
// Set LineProperties (only linewidthScaleFactor, interpreted as lineWidth)
var lineProperties = s_app ? s_app._cf.lineProperties.node : null;
if ( lineProperties )
{
this.stateManager.lineWidth( lineProperties._vf.linewidthScaleFactor );
}
else if ( x3dom.Utils.needLineWidth )
{
// Set Defaults
this.stateManager.lineWidth( 1 );
}
if ( shape.isSolid() && !twoSidedMat )
{
this.stateManager.enable( gl.CULL_FACE );
if ( shape.isCCW() )
{
this.stateManager.frontFace( gl.CCW );
}
else
{
this.stateManager.frontFace( gl.CW );
}
}
else
{
this.stateManager.disable( gl.CULL_FACE );
}
// transformation matrices
var model_view = mat_view.mult( transform );
var model_view_inv = model_view.inverse();
sp.screenWidth = this.canvas.width;
sp.isOrthoView = ( mat_proj._33 == 1 ) ? 1.0 : 0.0;
sp.modelMatrix = transform.toGL();
sp.modelViewMatrix = model_view.toGL();
sp.viewMatrix = mat_view.toGL();
sp.normalMatrix = model_view_inv.transpose().toGL();
sp.modelViewMatrixInverse = model_view_inv.toGL();
sp.modelViewProjectionMatrix = mat_scene.mult( transform ).toGL();
sp.modelViewProjectionInverseMatrix = mat_scene.mult( transform ).inverse().toGL();
sp.viewMatrixInverse = mat_view.inverse().toGL();
sp.cameraPosWS = mat_view.inverse().e3().toGL();
this.setTonemappingOperator( viewarea, sp );
// only calculate on "request" (maybe of interest for users)
// may be used by external materials
if ( isUserDefinedShader )
{
sp.model = transform.toGL();
sp.projectionMatrix = mat_proj.toGL();
sp.worldMatrix = transform.toGL();
sp.worldInverseTranspose = transform.inverse().transpose().toGL();
}
if ( this.VRMode == 2 )
{
var mat_view_R = viewarea.getViewMatrices()[ 1 ];
var mat_proj_R = viewarea.getProjectionMatrices()[ 1 ];
var mat_scene_R = mat_proj_R.mult( mat_view_R );
var model_view_R = mat_view_R.mult( transform );
var model_view_R_inv = model_view_R.inverse();
sp.viewMatrix2 = mat_view_R.toGL();
sp.modelViewMatrix2 = model_view_R.toGL();
sp.normalMatrix2 = model_view_R_inv.transpose().toGL();
sp.modelViewMatrixInverse2 = model_view_R_inv.toGL();
sp.modelViewProjectionMatrix2 = mat_scene_R.mult( transform ).toGL();
sp.isVR = 1.0;
}
else
{
sp.isVR = 0.0;
}
// PopGeometry: adapt LOD and set shader variables
if ( s_gl.popGeometry )
{
this.updatePopState( drawable, s_geo, sp, s_gl, scene, model_view, viewarea, this.x3dElem.runtime.fps );
}
for ( var cnt = 0, cnt_n = s_gl.texture.length; cnt < cnt_n; cnt++ )
{
tex = s_gl.texture[ cnt ];
gl.activeTexture( gl.TEXTURE0 + cnt );
gl.bindTexture( tex.type, tex.texture );
gl.texParameteri( tex.type, gl.TEXTURE_WRAP_S, tex.wrapS );
gl.texParameteri( tex.type, gl.TEXTURE_WRAP_T, tex.wrapT );
gl.texParameteri( tex.type, gl.TEXTURE_MAG_FILTER, tex.magFilter );
gl.texParameteri( tex.type, gl.TEXTURE_MIN_FILTER, tex.minFilter );
if ( x3dom.caps.ANISOTROPIC )
{
gl.texParameterf( tex.type, x3dom.caps.ANISOTROPIC.TEXTURE_MAX_ANISOTROPY_EXT, tex.anisotropicDegree );
}
if ( !shader || !isUserDefinedShader )
{
if ( !sp[ tex.samplerName ] )
{sp[ tex.samplerName ] = cnt;}
}
}
if ( x3dom.isa( mat, x3dom.nodeTypes.PhysicalMaterial ) &&
physicalEnvironmentLight != undefined &&
physicalEnvironmentLight._vf.diffuse != "" &&
physicalEnvironmentLight._vf.specular != "" )
{
var diffuseURL = physicalEnvironmentLight._nameSpace.getURL( physicalEnvironmentLight._vf.diffuse );
var specularURL = physicalEnvironmentLight._nameSpace.getURL( physicalEnvironmentLight._vf.specular );
var brdfURL = x3dom.BRDF_LUT;
//Get BRDF LUT Texture
var brdf_lut = this.cache.getTexture2D( gl, shape._nameSpace.doc, brdfURL, false, "anonymous", false, false, true );
var diffuse = this.cache.getTextureCube( gl, shape._nameSpace.doc, [ diffuseURL ], false, "anonymous", false, false, true );
var specular = this.cache.getTextureCube( gl, shape._nameSpace.doc, [ specularURL ], false, "anonymous", false, false, true );
gl.activeTexture( gl.TEXTURE0 + cnt_n );
gl.bindTexture( gl.TEXTURE_2D, brdf_lut );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE );
sp.brdfMap = cnt_n++;
if ( diffuse.ready )
{
gl.activeTexture( gl.TEXTURE0 + cnt_n );
gl.bindTexture( gl.TEXTURE_CUBE_MAP, diffuse );
gl.texParameteri( gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR );
gl.texParameteri( gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR );
gl.texParameteri( gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE );
gl.texParameteri( gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE );
sp.diffuseEnvironmentMap = cnt_n++;
}
if ( specular.ready )
{
gl.activeTexture( gl.TEXTURE0 + cnt_n );
gl.bindTexture( gl.TEXTURE_CUBE_MAP, specular );
gl.texParameteri( gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR );
gl.texParameteri( gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR );
gl.texParameteri( gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE );
gl.texParameteri( gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE );
sp.specularEnvironmentMap = cnt_n++;
}
}
if ( s_app && s_app._cf.textureTransform.node )
{
var texTrafo = s_app.texTransformMatrix();
sp.texTrafoMatrix = texTrafo.toGL();
}
// FIXME: what if geometry with split mesh has dynamic fields?
var attrib = null,
df,
df_n = s_gl.dynamicFields.length;
for ( df = 0; df < df_n; df++ )
{
attrib = s_gl.dynamicFields[ df ];
if ( sp[ attrib.name ] !== undefined )
{
gl.bindBuffer( gl.ARRAY_BUFFER, attrib.buf );
gl.vertexAttribPointer( sp[ attrib.name ], attrib.numComponents, gl.FLOAT, false, 0, 0 );
gl.enableVertexAttribArray( sp[ attrib.name ] );
}
}
// render object
var v,
v_n,
offset,
q_n;
var isParticleSet = false;
if ( x3dom.isa( s_geo, x3dom.nodeTypes.ParticleSet ) )
{
isParticleSet = true;
}
var pointProperties = s_app ? s_app._cf.pointProperties.node : null;
pointProperties = pointProperties && (
x3dom.isa( s_geo, x3dom.nodeTypes.PointSet ) ||
x3dom.isa( s_geo, x3dom.nodeTypes.BinaryGeometry )
);
if ( pointProperties )
{
var pprop_vf = s_app._cf.pointProperties.node._vf;
sp.pointSizeAttenuation = pprop_vf.attenuation.toGL();
sp.pointSizeFactor = pprop_vf.pointSizeScaleFactor;
sp.minPointSize = pprop_vf.pointSizeMinValue;
sp.maxPointSize = pprop_vf.pointSizeMaxValue;
}
q_n = s_gl.positions.length;
for ( var q = 0; q < q_n; q++ )
{
var q6 = 6 * q;
indicesReady = false;
if ( !( sp.position !== undefined && s_gl.buffers[ q6 + x3dom.BUFFER_IDX.POSITION ] && ( s_gl.indexes[ q ] ) ) )
{
continue;
}
if ( s_gl.buffers[ q6 + x3dom.BUFFER_IDX.INDEX ] )
{
if ( isParticleSet && s_geo.drawOrder() != "any" )
{ // sort
var indexArray,
zPos = [];
var pnts = s_geo._cf.coord.node.getPoints();
var pn = ( pnts.length == s_gl.indexes[ q ].length ) ? s_gl.indexes[ q ].length : 0;
for ( var i = 0; i < pn; i++ )
{
var center = model_view.multMatrixPnt( pnts[ i ] );
zPos.push( [ i, center.z ] );
}
if ( s_geo.drawOrder() == "backtofront" )
{
zPos.sort( function ( a, b )
{
return a[ 1 ] - b[ 1 ];
} );
}
else
{
zPos.sort( function ( b, a )
{
return a[ 1 ] - b[ 1 ];
} );
}
for ( i = 0; i < pn; i++ )
{
shape._webgl.indexes[ q ][ i ] = zPos[ i ][ 0 ];
}
if ( x3dom.caps.INDEX_UINT && ( pn > 65535 ) )
{
indexArray = new Uint32Array( shape._webgl.indexes[ q ] );
shape._webgl.indexType = gl.UNSIGNED_INT;
}
else
{
indexArray = new Uint16Array( shape._webgl.indexes[ q ] );
shape._webgl.indexType = gl.UNSIGNED_SHORT;
}
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, s_gl.buffers[ q6 + x3dom.BUFFER_IDX.INDEX ] );
gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, indexArray, gl.DYNAMIC_DRAW );
indexArray = null;
}
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, s_gl.buffers[ q6 + x3dom.BUFFER_IDX.INDEX ] );
indicesReady = true;
}
this.setVertexAttribEyeIdx( gl, sp );
this.setVertexAttribPointerPosition( gl, shape, q6, q );
this.setVertexAttribPointerNormal( gl, shape, q6, q );
this.setVertexAttribPointerTexCoord( gl, shape, q6, q );
this.setVertexAttribPointerTexCoord2( gl, shape, q6, q );
this.setVertexAttribPointerColor( gl, shape, q6, q );
this.setVertexAttribPointerTangent( gl, shape, q6, q );
this.setVertexAttribPointerBinormal( gl, shape, q6, q );
if ( ( sp.id !== undefined || sp.particleSize !== undefined ) && shape._webgl.buffers[ q6 + x3dom.BUFFER_IDX.ID ] )
{
gl.bindBuffer( gl.ARRAY_BUFFER, shape._webgl.buffers[ q6 + x3dom.BUFFER_IDX.ID ] );
// texture coordinate hack for IDs
if ( s_gl.binaryGeometry != 0 && s_geo._vf[ "idsPerVertex" ] == true )
{
gl.vertexAttribPointer( sp.id,
1, gl.FLOAT, false, 4, 0 );
gl.enableVertexAttribArray( sp.id );
}
else if ( isParticleSet )
{
gl.vertexAttribPointer( sp.particleSize,
3, gl.FLOAT, false, 0, 0 );
gl.enableVertexAttribArray( sp.particleSize );
}
}
if ( s_gl.popGeometry != 0 && s_gl.buffers[ q6 + x3dom.BUFFER_IDX.ID ] )
{
// special case: mimic gl_VertexID
gl.bindBuffer( gl.ARRAY_BUFFER, s_gl.buffers[ q6 + x3dom.BUFFER_IDX.ID ] );
gl.vertexAttribPointer( sp.PG_vertexID, 1, gl.FLOAT, false, 4, 0 );
gl.enableVertexAttribArray( sp.PG_vertexID );
}
// TODO: implement surface with additional wireframe render mode (independent from poly mode)
var indOff,
renderMode = viewarea.getRenderMode(),
self_transparent = mat ? ( mat._vf.transparency > 0 ) && ( !shape.isSolid() ) : false,
_drawFrontBack = function ( stateManager, drawFunction )
{
stateManager.frontFace( shape.isCCW() ? gl.CCW : gl.CW );
stateManager.enable( gl.CULL_FACE );
stateManager.cullFace( gl.FRONT );
drawFunction();
stateManager.cullFace( gl.BACK );
drawFunction();
stateManager.disable( gl.CULL_FACE );
};
if ( renderMode > 0 )
{
var polyMode = ( renderMode == 1 ) ? gl.POINTS : gl.LINES;
if ( indicesReady && ( s_gl.binaryGeometry > 0 || s_gl.popGeometry > 0 ) )
{
for ( v = 0, offset = 0, v_n = s_geo._vf.vertexCount.length; v < v_n; v++ )
{
this.drawElements( gl, polyMode, s_geo._vf.vertexCount[ v ], s_gl.indexType,
x3dom.Utils.getByteAwareOffset( offset, s_gl.indexType, gl ) );
offset += s_geo._vf.vertexCount[ v ];
}
}
else if ( s_gl.binaryGeometry < 0 || s_gl.popGeometry < 0 )
{
for ( v = 0, offset = 0, v_n = s_geo._vf.vertexCount.length; v < v_n; v++ )
{
this.drawArrays( gl, polyMode, offset, s_geo._vf.vertexCount[ v ] );
offset += s_geo._vf.vertexCount[ v ];
}
}
else if ( indicesReady && ( s_gl.bufferGeometry > 0 ) )
{
this.drawElements( gl, s_gl.primType[ 0 ], s_geo._vf.vertexCount[ 0 ], s_gl.indexType, shape._indexOffset );
}
else if ( s_gl.bufferGeometry < 0 )
{
this.drawArrays( gl, s_gl.primType[ 0 ], 0, s_geo._vf.vertexCount[ 0 ] );
}
else if ( s_geo.hasIndexOffset() )
{
// IndexedTriangleStripSet with primType TRIANGLE_STRIP,
// and Patch geometry from external BVHRefiner component
indOff = shape.tessellationProperties();
for ( v = 0, v_n = indOff.length; v < v_n; v++ )
{
this.drawElements( gl, polyMode, indOff[ v ].count, s_gl.indexType,
indOff[ v ].offset * x3dom.Utils.getOffsetMultiplier( s_gl.indexType, gl ) );
}
}
else if ( s_gl.indexes[ q ].length == 0 )
{
this.drawArrays( gl, polyMode, 0, s_gl.positions[ q ].length / 3 );
}
else
{
this.drawElements( gl, polyMode, s_gl.indexes[ q ].length, s_gl.indexType, 0 );
}
}
else
{
if ( indicesReady && ( s_gl.binaryGeometry > 0 || s_gl.popGeometry > 0 ) )
{
for ( v = 0, offset = 0, v_n = s_geo._vf.vertexCount.length; v < v_n; v++ )
{
this.drawElements( gl, s_gl.primType[ v ], s_geo._vf.vertexCount[ v ], s_gl.indexType,
x3dom.Utils.getByteAwareOffset( offset, s_gl.indexType, gl ) );
offset += s_geo._vf.vertexCount[ v ];
}
}
else if ( s_gl.binaryGeometry < 0 || s_gl.popGeometry < 0 )
{
for ( v = 0, offset = 0, v_n = s_geo._vf.vertexCount.length; v < v_n; v++ )
{
this.drawArrays( gl, s_gl.primType[ v ], offset, s_geo._vf.vertexCount[ v ] );
offset += s_geo._vf.vertexCount[ v ];
}
}
else if ( indicesReady && ( s_gl.bufferGeometry > 0 ) )
{
this.drawElements( gl, s_gl.primType[ 0 ], s_geo._vf.vertexCount[ 0 ], s_gl.indexType, shape._indexOffset );
}
else if ( s_gl.bufferGeometry < 0 )
{
this.drawArrays( gl, s_gl.primType[ 0 ], 0, s_geo._vf.vertexCount[ 0 ] );
}
else if ( s_geo.hasIndexOffset() )
{
// IndexedTriangleStripSet with primType TRIANGLE_STRIP,
// and Patch geometry from external BVHRefiner component
indOff = shape.tessellationProperties();
for ( v = 0, v_n = indOff.length; v < v_n; v++ )
{
this.drawElements( gl, s_gl.primType, indOff[ v ].count, s_gl.indexType,
indOff[ v ].offset * x3dom.Utils.getOffsetMultiplier( s_gl.indexType, gl ) );
}
}
else if ( s_gl.indexes[ q ].length == 0 )
{
if ( self_transparent )
{
_drawFrontBack(
this.stateManager,
this.drawArrays.bind( this, gl, s_gl.primType, 0, s_gl.positions[ q ].length / 3 )
);
}
else
{
this.drawArrays( gl, s_gl.primType, 0, s_gl.positions[ q ].length / 3 );
}
}
else
{
if ( self_transparent )
{
_drawFrontBack(
this.stateManager,
this.drawElements.bind( this, gl, s_gl.primType, s_gl.indexes[ q ].length, s_gl.indexType, 0 )
);
}
else
{
this.drawElements( gl, s_gl.primType, s_gl.indexes[ q ].length, s_gl.indexType, 0 );
}
}
}
// disable all used vertex attributes
gl.disableVertexAttribArray( sp.position );
if ( sp.normal !== undefined )
{
gl.disableVertexAttribArray( sp.normal );
}
this.disableVertexAttribEyeIdx( gl, sp );
if ( sp.texcoord !== undefined )
{
gl.disableVertexAttribArray( sp.texcoord );
}
if ( sp.texcoord2 !== undefined )
{
gl.disableVertexAttribArray( sp.texcoord2 );
}
if ( sp.color !== undefined )
{
gl.disableVertexAttribArray( sp.color );
}
if ( sp.tangent !== undefined )
{
gl.disableVertexAttribArray( sp.tangent );
}
if ( sp.binormal !== undefined )
{
gl.disableVertexAttribArray( sp.binormal );
}
if ( s_gl.buffers[ q6 + x3dom.BUFFER_IDX.ID ] )
{
if ( sp.id !== undefined )
{
gl.disableVertexAttribArray( sp.id );
}
else if ( sp.particleSize !== undefined )
{
gl.disableVertexAttribArray( sp.particleSize );
}
}
if ( s_gl.popGeometry != 0 && sp.PG_vertexID !== undefined )
{
gl.disableVertexAttribArray( sp.PG_vertexID ); // mimic gl_VertexID
}
} // end for loop over attrib arrays
for ( df = 0; df < df_n; df++ )
{
attrib = s_gl.dynamicFields[ df ];
if ( sp[ attrib.name ] !== undefined )
{
gl.disableVertexAttribArray( sp[ attrib.name ] );
}
}
// update stats
this.numCoords += s_msh._numCoords;
this.numFaces += s_msh._numFaces;
if ( s_gl.binaryGeometry || s_gl.popGeometry || s_gl.bufferGeometry )
{
this.numDrawCalls += s_geo._vf.vertexCount.length;
}
else if ( s_geo.hasIndexOffset() )
{
this.numDrawCalls += shape.tessellationProperties().length;
}
else
{
this.numDrawCalls += q_n;
}
// reset to default values for possibly user defined render states
this.stateManager.depthMask( true );
if ( depthMode )
{
this.stateManager.enable( gl.DEPTH_TEST );
this.stateManager.depthFunc( gl.LEQUAL );
this.stateManager.depthRange( 0, 1 );
}
if ( blendMode )
{
this.stateManager.enable( gl.BLEND );
this.stateManager.blendFuncSeparate( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE );
this.stateManager.blendColor( 1, 1, 1, 1 );
this.stateManager.blendEquation( gl.FUNC_ADD );
}
if ( colorMaskMode )
{
this.stateManager.colorMask( true, true, true, true );
}
if ( lineProperties )
{
this.stateManager.lineWidth( 1 );
}
// cleanup textures
var s_gl_tex = s_gl.texture;
cnt_n = s_gl_tex ? s_gl_tex.length : 0;
for ( cnt = 0; cnt < cnt_n; cnt++ )
{
if ( !s_gl_tex[ cnt ] )
{continue;}
if ( s_app && s_app._cf.texture.node )
{
tex = s_app._cf.texture.node.getTexture( cnt );
gl.activeTexture( gl.TEXTURE0 + cnt );
if ( x3dom.isa( tex, x3dom.nodeTypes.X3DEnvironmentTextureNode ) )
{
gl.bindTexture( gl.TEXTURE_CUBE_MAP, null );
}
else
{
gl.bindTexture( gl.TEXTURE_2D, null );
}
}
}
};
/**
* PopGeometry: adapt LOD and set shader variables
*
* @param drawable
* @param popGeo
* @param sp
* @param s_gl
* @param scene
* @param model_view
* @param viewarea
* @param currFps
*/
Context.prototype.updatePopState = function ( drawable, popGeo, sp, s_gl, scene, model_view, viewarea, currFps )
{
var tol = x3dom.nodeTypes.PopGeometry.ErrorToleranceFactor * popGeo._vf.precisionFactor;
if ( currFps <= 1 || viewarea.isMovingOrAnimating() )
{
tol *= x3dom.nodeTypes.PopGeometry.PrecisionFactorOnMove;
}
var currentLOD = 16;
if ( tol > 0 )
{
// BEGIN CLASSIC CODE
var viewpoint = scene.getViewpoint();
var imgPlaneHeightAtDistOne = viewpoint.getImgPlaneHeightAtDistOne();
var near = viewpoint.getNear();
var center = model_view.multMatrixPnt( popGeo._vf.position );
var tightRad = model_view.multMatrixVec( popGeo._vf.size ).length() * 0.5;
var largestRad = model_view.multMatrixVec( popGeo._vf.maxBBSize ).length() * 0.5;
// distance is estimated conservatively using the bounding sphere
var dist = Math.max( -center.z - tightRad, near );
var projPixelLength = dist * ( imgPlaneHeightAtDistOne / viewarea._height );
// compute LOD using bounding sphere
var arg = ( 2 * largestRad ) / ( tol * projPixelLength );
// END CLASSIC CODE
// BEGIN EXPERIMENTAL CODE
// compute LOD using screen-space coverage of bounding sphere
// TODO: the coverage should be distinct from priority
// var cov = drawable.priority;
// TODO: here, we need to decide whether we want to keep the ModF-encoding with
// respect to the largest bounding box... if not, change this and the shaders
// cov *= (popGeo._vf.maxBBSize.length() / popGeo._vf.size.length());
// var arg = cov / tol;
// END EXPERIMENTAL CODE
// use precomputed log(2.0) = 0.693147180559945
currentLOD = Math.ceil( Math.log( arg ) / 0.693147180559945 );
currentLOD = ( currentLOD < 1 ) ? 1 : ( ( currentLOD > 16 ) ? 16 : currentLOD );
}
// take care of user-controlled min and max values
var minPrec = popGeo._vf.minPrecisionLevel,
maxPrec = popGeo._vf.maxPrecisionLevel;
currentLOD = ( minPrec != -1 && currentLOD < minPrec ) ? minPrec : currentLOD;
currentLOD = ( maxPrec != -1 && currentLOD > maxPrec ) ? maxPrec : currentLOD;
// assign rendering resolution, according to currently loaded data and LOD
var currentLOD_min = ( s_gl.levelsAvailable < currentLOD ) ? s_gl.levelsAvailable : currentLOD;
currentLOD = currentLOD_min;
// TODO: only for demonstration purposes!!!
if ( tol <= 1 )
{currentLOD = ( currentLOD == popGeo.getNumLevels() ) ? 16 : currentLOD;}
// here, we tell X3DOM how many faces / vertices get displayed in the stats
var hasIndex = popGeo._vf.indexedRendering;
var p_msh = popGeo._mesh;
p_msh._numCoords = 0;
p_msh._numFaces = 0;
// TODO: this assumes pure TRIANGLES data (and gets overwritten from shadow/picking pass!!!)
for ( var i = 0; i < currentLOD_min; ++i )
{ // currentLOD breaks loop
var numVerticesAtLevel_i = s_gl.numVerticesAtLevel[ i ];
p_msh._numCoords += numVerticesAtLevel_i;
p_msh._numFaces += ( hasIndex ? popGeo.getNumIndicesByLevel( i ) : numVerticesAtLevel_i ) / 3;
}
x3dom.nodeTypes.PopGeometry.numRenderedVerts += p_msh._numCoords;
x3dom.nodeTypes.PopGeometry.numRenderedTris += p_msh._numFaces;
// this field is mainly thought for the use with external statistics
// TODO: does not work with instances
p_msh.currentLOD = currentLOD;
// here, we tell X3DOM how many vertices get rendered
// TODO: this assumes pure TRIANGLES data
popGeo.adaptVertexCount( hasIndex ? p_msh._numFaces * 3 : p_msh._numCoords );
// finally set shader variables...
sp.PG_maxBBSize = popGeo._vf.maxBBSize.toGL();
sp.PG_bbMin = popGeo._bbMinBySize; // floor(bbMin / maxBBSize)
sp.PG_numAnchorVertices = popGeo._vf.numAnchorVertices;
sp.PG_bbMaxModF = popGeo._vf.bbMaxModF.toGL();
sp.PG_bboxShiftVec = popGeo._vf.bbShiftVec.toGL();
sp.PG_precisionLevel = currentLOD;
// mimics Math.pow(2.0, 16.0 - currentLOD);
sp.PG_powPrecision = x3dom.nodeTypes.PopGeometry.powLUT[ currentLOD - 1 ];
};
/**
* Render ColorBuffer-Pass for picking
*
* @param viewarea
* @param x
* @param y
* @param buttonState
* @param viewMat
* @param sceneMat
* @returns {boolean}
*/
Context.prototype.pickValue = function ( viewarea, x, y, buttonState, viewMat, sceneMat )
{
x3dom.Utils.startMeasure( "picking" );
var scene = viewarea._scene;
var gl = this.ctx3d;
// method requires that scene has already been rendered at least once
if ( !gl || !scene || !scene._webgl || !scene.drawableCollection )
{
return false;
}
var pm = scene._vf.pickMode.toLowerCase();
var pickMode = 0;
switch ( pm )
{
case "box": return false;
case "idbuf": pickMode = 0; break;
case "idbuf24": pickMode = 3; break;
case "idbufid": pickMode = 4; break;
case "color": pickMode = 1; break;
case "texcoord": pickMode = 2; break;
}
// ViewMatrix and ViewProjectionMatrix
var mat_view,
mat_scene;
if ( arguments.length > 4 )
{
mat_view = viewMat;
mat_scene = sceneMat;
}
else
{
mat_view = viewarea._last_mat_view;
mat_scene = viewarea._last_mat_scene;
}
// remember correct scene bbox
var min = x3dom.fields.SFVec3f.copy( scene._lastMin );
var max = x3dom.fields.SFVec3f.copy( scene._lastMax );
// get current camera position
var from = mat_view.inverse().e3();
// get bbox of scene bbox and camera position
var _min = x3dom.fields.SFVec3f.copy( from );
var _max = x3dom.fields.SFVec3f.copy( from );
if ( _min.x > min.x ) { _min.x = min.x; }
if ( _min.y > min.y ) { _min.y = min.y; }
if ( _min.z > min.z ) { _min.z = min.z; }
if ( _max.x < max.x ) { _max.x = max.x; }
if ( _max.y < max.y ) { _max.y = max.y; }
if ( _max.z < max.z ) { _max.z = max.z; }
// temporarily set scene size to include camera
scene._lastMin.setValues( _min );
scene._lastMax.setValues( _max );
// get scalar scene size and adapted projection matrix
var sceneSize = scene._lastMax.subtract( scene._lastMin ).length();
//use zFar if set & closer to allow for smaller size
if ( scene.getViewpoint().getFar() )
{
sceneSize = Math.min( sceneSize, scene.getViewpoint().getFar() );
}
var cctowc = viewarea.getCCtoWCMatrix();
// restore correct scene bbox
scene._lastMin.setValues( min );
scene._lastMax.setValues( max );
// for deriving shadow ids together with shape ids
var baseID = x3dom.nodeTypes.Shape.objectID + 2;
// render to texture for reading pixel values
this.renderPickingPass( gl, scene, mat_view, mat_scene, from, sceneSize, pickMode, x, y, 2, 2 );
// the pixel values under mouse cursor
var pixelData = scene._webgl.fboPick.pixelData;
if ( pixelData && pixelData.length )
{
var pickPos = new x3dom.fields.SFVec3f( 0, 0, 0 ),
pickNorm = new x3dom.fields.SFVec3f( 0, 0, 1 ),
index = 0,
objId = pixelData[ index + 3 ],
shapeId,
pixelOffset = 1.0 / scene._webgl.pickScale,
denom = 1.0 / 256.0,
dist,
line,
lineoff,
right,
up;
if ( pickMode == 0 )
{
objId += 256 * pixelData[ index + 2 ];
dist = ( pixelData[ index ] / 255.0 ) * denom +
( pixelData[ index + 1 ] / 255.0 );
line = viewarea.calcViewRay( x, y, cctowc );
pickPos = from.add( line.dir.multiply( dist * sceneSize ) );
index = 4; // get right pixel
dist = ( pixelData[ index ] / 255.0 ) * denom +
( pixelData[ index + 1 ] / 255.0 );
lineoff = viewarea.calcViewRay( x + pixelOffset, y, cctowc );
right = from.add( lineoff.dir.multiply( dist * sceneSize ) );
right = right.subtract( pickPos ).normalize();
index = 8; // get top pixel
dist = ( pixelData[ index ] / 255.0 ) * denom +
( pixelData[ index + 1 ] / 255.0 );
lineoff = viewarea.calcViewRay( x, y - pixelOffset, cctowc );
up = from.add( lineoff.dir.multiply( dist * sceneSize ) );
up = up.subtract( pickPos ).normalize();
pickNorm = right.cross( up ).normalize();
}
else if ( pickMode == 3 )
{
objId += 256 * pixelData[ index + 2 ] +
65536 * pixelData[ index + 1 ];
dist = pixelData[ index ] / 255.0;
line = viewarea.calcViewRay( x, y, cctowc );
pickPos = from.add( line.dir.multiply( dist * sceneSize ) );
index = 4; // get right pixel
dist = pixelData[ index ] / 255.0;
lineoff = viewarea.calcViewRay( x + pixelOffset, y, cctowc );
right = from.add( lineoff.dir.multiply( dist * sceneSize ) );
right = right.subtract( pickPos ).normalize();
index = 8; // get top pixel
dist = pixelData[ index ] / 255.0;
lineoff = viewarea.calcViewRay( x, y - pixelOffset, cctowc );
up = from.add( lineoff.dir.multiply( dist * sceneSize ) );
up = up.subtract( pickPos ).normalize();
pickNorm = right.cross( up ).normalize();
}
else if ( pickMode == 4 )
{
objId += 256 * pixelData[ index + 2 ];
shapeId = pixelData[ index + 1 ];
shapeId += 256 * pixelData[ index ];
// check if standard shape picked without special shadow id
if ( objId == 0 && ( shapeId > 0 && shapeId < baseID ) )
{
objId = shapeId;
}
}
else
{
pickPos.x = pixelData[ index ];
pickPos.y = pixelData[ index + 1 ];
pickPos.z = pixelData[ index + 2 ];
}
// x3dom.debug.logInfo(pickPos + " / " + objId);
var eventType = "shadowObjectIdChanged",
shadowObjectIdChanged,
event;
var button = Math.max( buttonState >>> 8, buttonState & 255 );
if ( objId >= baseID )
{
objId -= baseID;
var hitObject;
var layerX = x * viewarea._inverseDevicePixelRatio;
var layerY = y * viewarea._inverseDevicePixelRatio;
if ( pickMode != 4 )
{
viewarea._pickingInfo.pickPos = pickPos;
viewarea._pick.setValues( pickPos );
viewarea._pickingInfo.pickNorm = pickNorm;
viewarea._pickNorm.setValues( pickNorm );
viewarea._pickingInfo.pickObj = null;
viewarea._pickingInfo.lastClickObj = null;
hitObject = scene._xmlNode;
}
else
{
viewarea._pickingInfo.pickObj = x3dom.nodeTypes.Shape.idMap.nodeID[ shapeId ];
hitObject = viewarea._pickingInfo.pickObj._xmlNode;
}
shadowObjectIdChanged = ( viewarea._pickingInfo.shadowObjectId != objId );
viewarea._pickingInfo.lastShadowObjectId = viewarea._pickingInfo.shadowObjectId;
viewarea._pickingInfo.shadowObjectId = objId;
// x3dom.debug.logInfo(baseID + " + " + objId);
if ( ( shadowObjectIdChanged || button ) && scene._xmlNode &&
( scene._xmlNode[ "on" + eventType ] || scene._xmlNode.hasAttribute( "on" + eventType ) ||
scene._listeners[ eventType ] ) )
{
event = {
target : scene._xmlNode,
type : eventType,
button : button, mouseup : ( ( buttonState >>> 8 ) > 0 ),
layerX : layerX, layerY : layerY,
shadowObjectId : objId,
worldX : pickPos.x, worldY : pickPos.y, worldZ : pickPos.z,
normalX : pickNorm.x, normalY : pickNorm.y, normalZ : pickNorm.z,
hitPnt : pickPos.toGL(),
hitObject : hitObject,
cancelBubble : false,
stopPropagation : function () { this.cancelBubble = true; },
preventDefault : function () { this.cancelBubble = true; }
};
scene.callEvtHandler( ( "on" + eventType ), event );
}
if ( scene._shadowIdMap && scene._shadowIdMap.mapping &&
objId < scene._shadowIdMap.mapping.length )
{
var shIds = scene._shadowIdMap.mapping[ objId ].usage,
n,
c,
shObj;
if ( !line )
{
line = viewarea.calcViewRay( x, y, cctowc );
}
// find corresponding dom tree object
for ( c = 0; c < shIds.length; c++ )
{
shObj = scene._nameSpace.defMap[ shIds[ c ] ];
// FIXME; bbox test too coarse (+ should include trafo)
if ( shObj && shObj.doIntersect( line ) )
{
viewarea._pickingInfo.pickObj = shObj;
break;
}
}
// Check for other namespaces e.g. Inline (FIXME; check recursively)
for ( n = 0; n < scene._nameSpace.childSpaces.length; n++ )
{
for ( c = 0; c < shIds.length; c++ )
{
shObj = scene._nameSpace.childSpaces[ n ].defMap[ shIds[ c ] ];
// FIXME; bbox test too coarse (+ should include trafo)
if ( shObj && shObj.doIntersect( line ) )
{
viewarea._pickingInfo.pickObj = shObj;
break;
}
}
}
}
}
else
{
shadowObjectIdChanged = ( viewarea._pickingInfo.shadowObjectId != -1 );
viewarea._pickingInfo.shadowObjectId = -1; // nothing hit
if ( shadowObjectIdChanged && scene._xmlNode &&
( scene._xmlNode[ "on" + eventType ] || scene._xmlNode.hasAttribute( "on" + eventType ) ||
scene._listeners[ eventType ] ) )
{
event = {
target : scene._xmlNode,
type : eventType,
button : button, mouseup : ( ( buttonState >>> 8 ) > 0 ),
layerX : layerX, layerY : layerY,
shadowObjectId : viewarea._pickingInfo.shadowObjectId,
cancelBubble : false,
stopPropagation : function () { this.cancelBubble = true; },
preventDefault : function () { this.cancelBubble = true; }
};
scene.callEvtHandler( ( "on" + eventType ), event );
}
if ( objId > 0 )
{
// x3dom.debug.logInfo(x3dom.nodeTypes.Shape.idMap.nodeID[objId]._DEF + " // " +
// x3dom.nodeTypes.Shape.idMap.nodeID[objId]._xmlNode.localName);
viewarea._pickingInfo.pickPos = pickPos;
viewarea._pickingInfo.pickNorm = pickNorm;
viewarea._pickingInfo.pickObj = x3dom.nodeTypes.Shape.idMap.nodeID[ objId ];
}
else
{
viewarea._pickingInfo.pickObj = null;
// viewarea._pickingInfo.lastObj = null;
viewarea._pickingInfo.lastClickObj = null;
}
}
}
var pickTime = x3dom.Utils.stopMeasure( "picking" );
this.x3dElem.runtime.addMeasurement( "PICKING", pickTime );
return true;
};
/**
* Render ColorBuffer-Pass for picking sub window
*
* @param viewarea
* @param x1
* @param y1
* @param x2
* @param y2
* @returns {*}
*/
Context.prototype.pickRect = function ( viewarea, x1, y1, x2, y2 )
{
var gl = this.ctx3d;
var scene = viewarea ? viewarea._scene : null;
// method requires that scene has already been rendered at least once
if ( !gl || !scene || !scene._webgl || !scene.drawableCollection )
{return false;}
// values not fully correct but unnecessary anyway, just to feed the shader
var from = viewarea._last_mat_view.inverse().e3();
var sceneSize = scene._lastMax.subtract( scene._lastMin ).length();
var x = ( x1 <= x2 ) ? x1 : x2;
var y = ( y1 >= y2 ) ? y1 : y2;
var width = ( 1 + Math.abs( x2 - x1 ) ) * scene._webgl.pickScale;
var height = ( 1 + Math.abs( y2 - y1 ) ) * scene._webgl.pickScale;
// render to texture for reading pixel values
this.renderPickingPass( gl, scene, viewarea._last_mat_view, viewarea._last_mat_scene,
from, sceneSize, 0, x, y, ( width < 1 ) ? 1 : width, ( height < 1 ) ? 1 : height );
var index;
var pickedObjects = [];
// get objects in rectangle
for ( index = 0; scene._webgl.fboPick.pixelData &&
index < scene._webgl.fboPick.pixelData.length; index += 4 )
{
var objId = scene._webgl.fboPick.pixelData[ index + 3 ] +
scene._webgl.fboPick.pixelData[ index + 2 ] * 256;
if ( objId > 0 )
{pickedObjects.push( objId );}
}
pickedObjects.sort();
// make found object IDs unique
var pickedObjectsTemp = ( function ( arr )
{
var a = [],
l = arr.length;
for ( var i = 0; i < l; i++ )
{
for ( var j = i + 1; j < l; j++ )
{
if ( arr[ i ] === arr[ j ] )
{j = ++i;}
}
a.push( arr[ i ] );
}
return a;
} )( pickedObjects );
pickedObjects = pickedObjectsTemp;
var pickedNode,
pickedNodes = [],
hitObject;
// for deriving shadow ids together with shape ids
var baseID = x3dom.nodeTypes.Shape.objectID + 2;
var partIDs = [];
for ( index = 0; index < pickedObjects.length; index++ )
{
objId = pickedObjects[ index ];
if ( objId >= baseID )
{
objId -= baseID;
}
else
{
hitObject = x3dom.nodeTypes.Shape.idMap.nodeID[ objId ];
hitObject = ( hitObject && hitObject._xmlNode ) ? hitObject._xmlNode : null;
if ( hitObject )
{pickedNodes.push( hitObject );}
}
}
return pickedNodes;
};
/**
* Render Scene (Main-Pass)
*
* @param viewarea
*/
Context.prototype.renderScene = function ( viewarea, vrFrameData )
{
var gl = this.ctx3d;
var scene = viewarea._scene;
if ( gl === null || scene === null )
{
return;
}
var rentex = viewarea._doc._nodeBag.renderTextures;
var rt_tex,
rtl_i,
rtl_n = rentex.length;
var texProp = null;
// for initFBO
var type = gl.UNSIGNED_BYTE;
var shadowType = gl.UNSIGNED_BYTE;
var nearestFilt = false;
if ( x3dom.caps.FP_TEXTURES )
{
type = gl.FLOAT;
shadowType = gl.FLOAT;
if ( !x3dom.caps.FPL_TEXTURES )
{
// TODO: use correct filtering for fp-textures
nearestFilt = true;
}
}
var shadowedLights,
numShadowMaps,
i,
j,
n,
size,
sizeAvailable,
texType,
refinementPos;
var vertices = [ -1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1 ];
scene.updateVolume();
if ( !scene._webgl )
{
scene._webgl = {};
this.setupFgnds( gl, scene );
// scale factor for mouse coords and width/ height (low res for speed-up)
scene._webgl.pickScale = 0.5;
scene._webgl._currFboWidth = Math.round( this.canvas.width * scene._webgl.pickScale );
scene._webgl._currFboHeight = Math.round( this.canvas.height * scene._webgl.pickScale );
// TODO: FIXME when spec ready: readPixels not (yet?) available for float textures
// https://bugzilla.mozilla.org/show_bug.cgi?id=681903
// https://www.khronos.org/webgl/public-mailing-list/archives/1108/msg00025.html
scene._webgl.fboPick = x3dom.Utils.initFBO( gl,
scene._webgl._currFboWidth, scene._webgl._currFboHeight, gl.UNSIGNED_BYTE, false, true );
scene._webgl.fboPick.pixelData = null;
// Set picking shaders
/*
scene._webgl.pickShader = this.cache.getShader(gl, x3dom.shader.PICKING);
scene._webgl.pickShader24 = this.cache.getShader(gl, x3dom.shader.PICKING_24);
scene._webgl.pickShaderId = this.cache.getShader(gl, x3dom.shader.PICKING_ID);
scene._webgl.pickColorShader = this.cache.getShader(gl, x3dom.shader.PICKING_COLOR);
scene._webgl.pickTexCoordShader = this.cache.getShader(gl, x3dom.shader.PICKING_TEXCOORD);
*/
scene._webgl.normalShader = this.cache.getShader( gl, x3dom.shader.NORMAL );
// Initialize shadow maps
scene._webgl.fboShadow = [];
shadowedLights = viewarea.getShadowedLights();
n = shadowedLights.length;
for ( i = 0; i < n; i++ )
{
size = shadowedLights[ i ]._vf.shadowMapSize;
if ( !x3dom.isa( shadowedLights[ i ], x3dom.nodeTypes.PointLight ) )
{
// cascades for directional lights
numShadowMaps = Math.max( 1, Math.min( shadowedLights[ i ]._vf.shadowCascades, 6 ) );
}
else
{
// six maps for point lights
numShadowMaps = 6;
}
scene._webgl.fboShadow[ i ] = [];
for ( j = 0; j < numShadowMaps; j++ )
{scene._webgl.fboShadow[ i ][ j ] = x3dom.Utils.initFBO( gl, size, size, shadowType, false, true );}
}
if ( scene._webgl.fboShadow.length > 0 || x3dom.SSAO.isEnabled( scene ) )
{scene._webgl.fboScene = x3dom.Utils.initFBO( gl, this.canvas.width, this.canvas.height, shadowType, false, true );}
scene._webgl.fboBlur = [];
// initialize blur fbo (different fbos for different sizes)
for ( i = 0; i < n; i++ )
{
size = scene._webgl.fboShadow[ i ][ 0 ].height;
sizeAvailable = false;
for ( j = 0; j < scene._webgl.fboBlur.length; j++ )
{
if ( size == scene._webgl.fboBlur[ j ].height )
{sizeAvailable = true;}
}
if ( !sizeAvailable )
{scene._webgl.fboBlur[ scene._webgl.fboBlur.length ] = x3dom.Utils.initFBO( gl, size, size, shadowType, false, true );}
}
// initialize Data for post processing
scene._webgl.ppBuffer = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER, scene._webgl.ppBuffer );
gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( vertices ), gl.STATIC_DRAW );
// scene._webgl.shadowShader = this.cache.getShader(gl, x3dom.shader.SHADOW);
// TODO: cleanup on shutdown and lazily create on first use like size-dependent variables below
scene._webgl.refinement = {
stamps : new Array( 2 ),
positionBuffer : gl.createBuffer()
};
gl.bindBuffer( gl.ARRAY_BUFFER, scene._webgl.refinement.positionBuffer );
gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( vertices ), gl.STATIC_DRAW );
// This must be refreshed on node change!
for ( rtl_i = 0; rtl_i < rtl_n; rtl_i++ )
{
rt_tex = rentex[ rtl_i ];
texProp = rt_tex._cf.textureProperties.node;
texType = rt_tex.requirePingPong() ? gl.UNSIGNED_BYTE : type;
rt_tex._webgl = {};
rt_tex._webgl.fbo = x3dom.Utils.initFBO( gl,
rt_tex._vf.dimensions[ 0 ], rt_tex._vf.dimensions[ 1 ], texType,
( texProp && texProp._vf.generateMipMaps ), rt_tex._vf.depthMap || !rt_tex.requirePingPong() );
rt_tex._cleanupGLObjects = function ( retainTex )
{
if ( !retainTex )
{gl.deleteTexture( this._webgl.fbo.tex );}
if ( this._webgl.fbo.dtex )
{gl.deleteTexture( this._webgl.fbo.dtex );}
if ( this._webgl.fbo.rbo )
{gl.deleteFramebuffer( this._webgl.fbo.rbo );}
gl.bindFramebuffer( gl.FRAMEBUFFER, null );
gl.deleteFramebuffer( this._webgl.fbo.fbo );
this._webgl.fbo.rbo = null;
this._webgl.fbo.fbo = null;
};
if ( rt_tex.requirePingPong() )
{
refinementPos = rt_tex._vf.dimensions[ 0 ] + "x" + rt_tex._vf.dimensions[ 1 ];
if ( scene._webgl.refinement[ refinementPos ] === undefined )
{
scene._webgl.refinement[ refinementPos ] = x3dom.Utils.initFBO( gl,
rt_tex._vf.dimensions[ 0 ], rt_tex._vf.dimensions[ 1 ], texType, false, false );
}
rt_tex._webgl.texture = null;
}
}
viewarea._last_mat_view = x3dom.fields.SFMatrix4f.identity();
viewarea._last_mat_proj = x3dom.fields.SFMatrix4f.identity();
viewarea._last_mat_scene = x3dom.fields.SFMatrix4f.identity();
this._calledViewpointChangedHandler = false;
}
else
{
// updates needed?
var fboWidth = Math.round( this.canvas.width * scene._webgl.pickScale );
var fboHeight = Math.round( this.canvas.height * scene._webgl.pickScale );
if ( scene._webgl._currFboWidth !== fboWidth ||
scene._webgl._currFboHeight !== fboHeight )
{
scene._webgl._currFboWidth = fboWidth;
scene._webgl._currFboHeight = fboHeight;
scene._webgl.fboPick = x3dom.Utils.initFBO( gl, fboWidth, fboHeight, scene._webgl.fboPick.type, false, true );
scene._webgl.fboPick.pixelData = null;
x3dom.debug.logInfo( "Refreshed picking FBO to size (" + fboWidth + ", " + fboHeight + ")" );
}
for ( rtl_i = 0; rtl_i < rtl_n; rtl_i++ )
{
rt_tex = rentex[ rtl_i ];
if ( rt_tex._webgl && rt_tex._webgl.fbo &&
rt_tex._webgl.fbo.width == rt_tex._vf.dimensions[ 0 ] &&
rt_tex._webgl.fbo.height == rt_tex._vf.dimensions[ 1 ] )
{continue;}
rt_tex.invalidateGLObject();
if ( rt_tex._cleanupGLObjects )
{
rt_tex._cleanupGLObjects();
}
else
{
rt_tex._cleanupGLObjects = function ( retainTex )
{
if ( !retainTex )
{gl.deleteTexture( this._webgl.fbo.tex );}
if ( this._webgl.fbo.dtex )
{gl.deleteTexture( this._webgl.fbo.dtex );}
if ( this._webgl.fbo.rbo )
{gl.deleteRenderbuffer( this._webgl.fbo.rbo );}
gl.bindFramebuffer( gl.FRAMEBUFFER, null );
gl.deleteFramebuffer( this._webgl.fbo.fbo );
this._webgl.fbo.rbo = null;
this._webgl.fbo.fbo = null;
};
}
texProp = rt_tex._cf.textureProperties.node;
texType = rt_tex.requirePingPong() ? gl.UNSIGNED_BYTE : type;
rt_tex._webgl = {};
rt_tex._webgl.fbo = x3dom.Utils.initFBO( gl,
rt_tex._vf.dimensions[ 0 ], rt_tex._vf.dimensions[ 1 ], texType,
( texProp && texProp._vf.generateMipMaps ), rt_tex._vf.depthMap || !rt_tex.requirePingPong() );
if ( rt_tex.requirePingPong() )
{
refinementPos = rt_tex._vf.dimensions[ 0 ] + "x" + rt_tex._vf.dimensions[ 1 ];
if ( scene._webgl.refinement[ refinementPos ] === undefined )
{
scene._webgl.refinement[ refinementPos ] = x3dom.Utils.initFBO( gl,
rt_tex._vf.dimensions[ 0 ], rt_tex._vf.dimensions[ 1 ], texType, false, false );
}
rt_tex._webgl.texture = null;
}
x3dom.debug.logInfo( "Init/resize RenderedTexture_" + rtl_i + " to size " +
rt_tex._vf.dimensions[ 0 ] + " x " + rt_tex._vf.dimensions[ 1 ] );
}
// reinitialize shadow fbos if necessary
shadowedLights = viewarea.getShadowedLights();
n = shadowedLights.length;
for ( i = 0; i < n; i++ )
{
size = shadowedLights[ i ]._vf.shadowMapSize;
if ( !x3dom.isa( shadowedLights[ i ], x3dom.nodeTypes.PointLight ) )
{
// cascades for directional lights
numShadowMaps = Math.max( 1, Math.min( shadowedLights[ i ]._vf.shadowCascades, 6 ) );
}
else
{
// six maps for point lights
numShadowMaps = 6;
}
if ( typeof scene._webgl.fboShadow[ i ] === "undefined" ||
scene._webgl.fboShadow[ i ].length != numShadowMaps ||
scene._webgl.fboShadow[ i ][ 0 ].height != size )
{
scene._webgl.fboShadow[ i ] = [];
for ( j = 0; j < numShadowMaps; j++ )
{
scene._webgl.fboShadow[ i ][ j ] = x3dom.Utils.initFBO( gl, size, size, shadowType, false, true );
}
}
}
// reinitialize blur fbos if necessary
for ( i = 0; i < n; i++ )
{
size = scene._webgl.fboShadow[ i ][ 0 ].height;
sizeAvailable = false;
for ( j = 0; j < scene._webgl.fboBlur.length; j++ )
{
if ( size == scene._webgl.fboBlur[ j ].height )
{sizeAvailable = true;}
}
if ( !sizeAvailable )
{scene._webgl.fboBlur[ scene._webgl.fboBlur.length ] = x3dom.Utils.initFBO( gl, size, size, shadowType, false, true );}
}
if ( ( x3dom.SSAO.isEnabled( scene ) || scene._webgl.fboShadow.length > 0 ) && typeof scene._webgl.fboScene == "undefined" || scene._webgl.fboScene &&
( this.canvas.width != scene._webgl.fboScene.width || this.canvas.height != scene._webgl.fboScene.height ) )
{
scene._webgl.fboScene = x3dom.Utils.initFBO( gl, this.canvas.width, this.canvas.height, shadowType, false, true );
}
}
var env = scene.getEnvironment();
// update internal flags
env.checkSanity();
var bgnd = scene.getBackground();
// setup or update bgnd
this.setupScene( gl, bgnd );
this.numFaces = 0;
this.numCoords = 0;
this.numDrawCalls = 0;
var mat_proj = viewarea.getProjectionMatrix();
var mat_view = viewarea.getViewMatrix();
// fire viewpointChanged event
if ( !this._calledViewpointChangedHandler || !viewarea._last_mat_view.equals( mat_view ) )
{
var e_viewpoint = scene.getViewpoint();
var e_eventType = "viewpointChanged";
try
{
if ( e_viewpoint.hasEventListener( e_eventType ) || scene.hasEventListener( e_eventType ) )
{
var e_viewtrafo = e_viewpoint.getCurrentTransform();
e_viewtrafo = e_viewtrafo.inverse().mult( mat_view );
var e_mat = e_viewtrafo.inverse();
var e_rotation = new x3dom.fields.Quaternion( 0, 0, 1, 0 );
e_rotation.setValue( e_mat );
var e_translation = e_mat.e3();
var e_event = {
target : e_viewpoint._xmlNode,
type : e_eventType,
matrix : e_viewtrafo,
position : e_translation,
orientation : e_rotation.toAxisAngle(),
cancelBubble : false,
stopPropagation : function () { this.cancelBubble = true; },
preventDefault : function () { this.cancelBubble = true; }
};
if ( e_viewpoint.hasEventListener( e_eventType ) )
{
e_viewpoint.callEvtHandler( ( "on" + e_eventType ), e_event );
}
if ( scene.hasEventListener( e_eventType ) )
{
scene.callEvtHandler( ( "on" + e_eventType ), e_event );
}
e_viewpoint.callEvtHandler( ( "on" + e_eventType ), e_event );
this._calledViewpointChangedHandler = true;
}
}
catch ( e_e )
{
x3dom.debug.logException( e_e );
}
}
viewarea._last_mat_view = mat_view;
viewarea._last_mat_proj = mat_proj;
var mat_scene = mat_proj.mult( mat_view ); // viewarea.getWCtoCCMatrix();
viewarea._last_mat_scene = mat_scene;
// Collect drawables (traverse)
scene.drawableCollection = null; // Always update needed?
if ( !scene.drawableCollection )
{
var drawableCollectionConfig = {
viewArea : viewarea,
sortTrans : env._vf.sortTrans,
viewMatrix : mat_view,
projMatrix : mat_proj,
sceneMatrix : mat_scene,
frustumCulling : true,
smallFeatureThreshold : env._smallFeatureThreshold,
context : this,
gl : gl
};
scene.drawableCollection = new x3dom.DrawableCollection( drawableCollectionConfig );
x3dom.Utils.startMeasure( "traverse" );
scene.collectDrawableObjects( x3dom.fields.SFMatrix4f.identity(), scene.drawableCollection, true, false, 0, [] );
var traverseTime = x3dom.Utils.stopMeasure( "traverse" );
this.x3dElem.runtime.addMeasurement( "TRAVERSE", traverseTime );
}
// Sort drawables
x3dom.Utils.startMeasure( "sorting" );
scene.drawableCollection.sort();
var sortTime = x3dom.Utils.stopMeasure( "sorting" );
this.x3dElem.runtime.addMeasurement( "SORT", sortTime );
// Render Shadow Pass
var slights = viewarea.getLights(),
numLights = slights.length,
mat_light;
var WCToLCMatrices = [];
var lMatrices = [];
var shadowCount = 0;
x3dom.Utils.startMeasure( "shadow" );
var sLightMatrix = viewarea.getLightMatrix( slights );
for ( var p = 0; p < numLights; p++ )
{
if ( slights[ p ]._vf.shadowIntensity > 0.0 )
{
var lightMatrix = sLightMatrix[ p ];
var shadowMaps = scene._webgl.fboShadow[ shadowCount ];
var offset = Math.max( 0.0, Math.min( 1.0, slights[ p ]._vf.shadowOffset ) );
if ( !x3dom.isa( slights[ p ], x3dom.nodeTypes.PointLight ) )
{
// get cascade count
var numCascades = Math.max( 1, Math.min( slights[ p ]._vf.shadowCascades, 6 ) );
// calculate transformation matrices
mat_light = viewarea.getWCtoLCMatricesCascaded( lightMatrix, slights[ p ], mat_proj );
// render shadow pass
for ( i = 0; i < numCascades; i++ )
{
this.renderShadowPass( gl, viewarea, mat_light[ i ], mat_view, shadowMaps[ i ], offset, false );
}
}
else
{
// for point lights 6 render passes
mat_light = viewarea.getWCtoLCMatricesPointLight( lightMatrix, slights[ p ], mat_proj );
for ( i = 0; i < 6; i++ )
{
this.renderShadowPass( gl, viewarea, mat_light[ i ], mat_view, shadowMaps[ i ], offset, false );
}
}
shadowCount++;
// save transformations for shadow rendering
WCToLCMatrices[ WCToLCMatrices.length ] = mat_light;
lMatrices[ lMatrices.length ] = lightMatrix;
}
}
// One pass for depth of scene from camera view (to enable post-processing shading)
if ( shadowCount > 0 || x3dom.SSAO.isEnabled( scene ) )
{
this.renderShadowPass( gl, viewarea, mat_scene, mat_view, scene._webgl.fboScene, 0.0, true );
var shadowTime = x3dom.Utils.stopMeasure( "shadow" );
this.x3dElem.runtime.addMeasurement( "SHADOW", shadowTime );
}
else
{
this.x3dElem.runtime.removeMeasurement( "SHADOW" );
}
mat_light = viewarea.getWCtoLCMatrix( viewarea.getLightMatrix()[ 0 ] );
for ( rtl_i = 0; rtl_i < rtl_n; rtl_i++ )
{
this.renderRTPass( gl, viewarea, rentex[ rtl_i ] );
}
// rendering
x3dom.Utils.startMeasure( "render" );
this.stateManager.bindFramebuffer( gl.FRAMEBUFFER, vrFrameData ? vrFrameData.framebuffer : null );
this.stateManager.viewport( 0, 0, this.canvas.width, this.canvas.height );
// calls gl.clear etc. (bgnd stuff)
bgnd._webgl.render( gl, mat_view, mat_proj, viewarea );
x3dom.nodeTypes.PopGeometry.numRenderedVerts = 0;
x3dom.nodeTypes.PopGeometry.numRenderedTris = 0;
n = scene.drawableCollection.length;
// Very, very experimental priority culling, currently coupled with frustum and small feature culling
// TODO: what about shadows?
if ( env._vf.smallFeatureCulling && env._lowPriorityThreshold < 1 && viewarea.isMovingOrAnimating() )
{
n = Math.floor( n * env._lowPriorityThreshold );
if ( !n && scene.drawableCollection.length )
{n = 1;} // render at least one object
}
this.stateManager.unsetProgram();
// render all remaining shapes
for ( i = 0; i < n; i++ )
{
var drawable = scene.drawableCollection.get( i );
this.renderShape( drawable, viewarea, slights, numLights, mat_view, mat_scene, mat_light, mat_proj, gl );
}
if ( shadowCount > 0 )
{this.renderShadows( gl, viewarea, shadowedLights, WCToLCMatrices, lMatrices, mat_view, mat_proj, mat_scene );}
this.stateManager.disable( gl.BLEND );
this.stateManager.disable( gl.DEPTH_TEST );
viewarea._numRenderedNodes = n;
if ( x3dom.SSAO.isEnabled( scene ) )
{x3dom.SSAO.renderSSAO( this.stateManager, gl, scene, this.canvas );}
// if _visDbgBuf then show helper buffers in foreground for debugging
if ( viewarea._visDbgBuf !== undefined && viewarea._visDbgBuf )
{
var pm = scene._vf.pickMode.toLowerCase();
if ( pm.indexOf( "idbuf" ) == 0 || pm == "color" || pm == "texcoord" )
{
this.stateManager.viewport( 0, 3 * this.canvas.height / 4,
this.canvas.width / 4, this.canvas.height / 4 );
scene._fgnd._webgl.render( gl, scene._webgl.fboPick.tex );
}
if ( shadowCount > 0 || x3dom.SSAO.isEnabled( scene ) )
{
this.stateManager.viewport( this.canvas.width / 4, 3 * this.canvas.height / 4,
this.canvas.width / 4, this.canvas.height / 4 );
scene._fgnd._webgl.render( gl, scene._webgl.fboScene.tex );
}
var row = 3,
col = 2;
for ( i = 0; i < shadowCount; i++ )
{
var shadowMaps = scene._webgl.fboShadow[ i ];
for ( j = 0; j < shadowMaps.length; j++ )
{
this.stateManager.viewport( col * this.canvas.width / 4, row * this.canvas.height / 4,
this.canvas.width / 4, this.canvas.height / 4 );
scene._fgnd._webgl.render( gl, shadowMaps[ j ].tex );
if ( col < 2 )
{
col++;
}
else
{
col = 0;
row--;
}
}
}
for ( rtl_i = 0; rtl_i < rtl_n; rtl_i++ )
{
rt_tex = rentex[ rtl_i ];
if ( !rt_tex._webgl.fbo.fbo ) // might be deleted (--> RefinementTexture when finished)
{continue;}
this.stateManager.viewport( rtl_i * this.canvas.width / 8, 5 * this.canvas.height / 8,
this.canvas.width / 8, this.canvas.height / 8 );
scene._fgnd._webgl.render( gl, rt_tex._webgl.fbo.tex );
}
}
gl.finish();
// gl.flush();
var renderTime = x3dom.Utils.stopMeasure( "render" );
this.x3dElem.runtime.addMeasurement( "RENDER", renderTime );
this.x3dElem.runtime.addMeasurement( "DRAW", ( n ? renderTime / n : 0 ) );
this.x3dElem.runtime.addInfo( "#NODES:", scene.drawableCollection.numberOfNodes );
this.x3dElem.runtime.addInfo( "#SHAPES:", viewarea._numRenderedNodes );
this.x3dElem.runtime.addInfo( "#DRAWS:", this.numDrawCalls );
this.x3dElem.runtime.addInfo( "#POINTS:", this.numCoords );
this.x3dElem.runtime.addInfo( "#TRIS:", this.numFaces );
// scene.drawableObjects = null;
};
/**
* Render special PingPong-Pass
*
* @param gl
* @param viewarea
* @param rt
*/
Context.prototype.renderPingPongPass = function ( gl, viewarea, rt )
{
var scene = viewarea._scene;
var refinementPos = rt._vf.dimensions[ 0 ] + "x" + rt._vf.dimensions[ 1 ];
var refinementFbo = scene._webgl.refinement[ refinementPos ];
// load stamp textures
if ( rt._currLoadLevel == 0 && ( !scene._webgl.refinement.stamps[ 0 ] || !scene._webgl.refinement.stamps[ 1 ] ) )
{
scene._webgl.refinement.stamps[ 0 ] = this.cache.getTexture2D( gl, rt._nameSpace.doc,
rt._nameSpace.getURL( rt._vf.stamp0 ), false, "anonymous", false, false );
scene._webgl.refinement.stamps[ 1 ] = this.cache.getTexture2D( gl, rt._nameSpace.doc,
rt._nameSpace.getURL( rt._vf.stamp1 ), false, "anonymous", false, false );
}
// load next level of image
if ( rt._currLoadLevel < rt._loadLevel )
{
rt._currLoadLevel++;
if ( rt._webgl.texture )
{gl.deleteTexture( rt._webgl.texture );}
var filename = rt._vf.url[ 0 ] + "/" + rt._currLoadLevel + "." + rt._vf.format;
rt._webgl.texture = x3dom.Utils.createTexture2D( gl, rt._nameSpace.doc,
rt._nameSpace.getURL( filename ), false, "anonymous", false, false );
if ( rt._vf.iterations % 2 === 0 )
{
( rt._currLoadLevel % 2 !== 0 ) ? rt._repeat.x *= 2.0 : rt._repeat.y *= 2.0;
}
else
{
( rt._currLoadLevel % 2 === 0 ) ? rt._repeat.x *= 2.0 : rt._repeat.y *= 2.0;
}
}
if ( !rt._webgl.texture.ready ||
!scene._webgl.refinement.stamps[ 0 ].ready || !scene._webgl.refinement.stamps[ 1 ].ready )
{return;}
// first pass
this.stateManager.bindFramebuffer( gl.FRAMEBUFFER, refinementFbo.fbo );
this.stateManager.viewport( 0, 0, refinementFbo.width, refinementFbo.height );
this.stateManager.disable( gl.BLEND );
this.stateManager.disable( gl.CULL_FACE );
this.stateManager.disable( gl.DEPTH_TEST );
gl.clearColor( 0, 0, 0, 1 );
gl.clearDepth( 1 );
gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
var sp = this.cache.getShader( gl, x3dom.shader.TEXTURE_REFINEMENT );
this.stateManager.useProgram( sp );
gl.bindBuffer( gl.ARRAY_BUFFER, scene._webgl.refinement.positionBuffer );
gl.vertexAttribPointer( sp.position, 2, gl.FLOAT, false, 0, 0 );
gl.enableVertexAttribArray( sp.position );
sp.stamp = 0;
gl.activeTexture( gl.TEXTURE0 );
gl.bindTexture( gl.TEXTURE_2D, scene._webgl.refinement.stamps[ ( rt._currLoadLevel + 1 ) % 2 ] ); // draw stamp
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT );
if ( rt._currLoadLevel > 1 )
{
sp.lastTex = 1;
gl.activeTexture( gl.TEXTURE1 );
gl.bindTexture( gl.TEXTURE_2D, rt._webgl.fbo.tex );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE );
}
sp.curTex = 2;
gl.activeTexture( gl.TEXTURE2 );
gl.bindTexture( gl.TEXTURE_2D, rt._webgl.texture ); // draw level image to fbo
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE );
sp.mode = rt._currLoadLevel - 1;
sp.repeat = rt._repeat.toGL();
gl.drawArrays( gl.TRIANGLES, 0, 6 );
// second pass
this.stateManager.bindFramebuffer( gl.FRAMEBUFFER, rt._webgl.fbo.fbo );
gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
sp.mode = 0;
sp.curTex = 2;
gl.activeTexture( gl.TEXTURE2 );
gl.bindTexture( gl.TEXTURE_2D, refinementFbo.tex ); // draw result to fbo
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE );
gl.drawArrays( gl.TRIANGLES, 0, 6 );
gl.activeTexture( gl.TEXTURE0 );
gl.bindTexture( gl.TEXTURE_2D, null );
gl.disableVertexAttribArray( sp.position );
// pass done
this.stateManager.bindFramebuffer( gl.FRAMEBUFFER, null );
this.stateManager.viewport( 0, 0, this.canvas.width, this.canvas.height );
if ( rt._vf.autoRefinement )
{rt.nextLevel();}
if ( rt._currLoadLevel == rt._vf.maxLevel )
{rt._currLoadLevel++;}
if ( rt._webgl.fbo.mipMap )
{
gl.bindTexture( gl.TEXTURE_2D, rt._webgl.fbo.tex );
gl.generateMipmap( gl.TEXTURE_2D );
gl.bindTexture( gl.TEXTURE_2D, null );
}
// we're finally done: cleanup/delete all helper FBOs
if ( !rt.requirePingPong() )
{
gl.deleteTexture( rt._webgl.texture );
delete rt._webgl.texture;
rt._cleanupGLObjects( true );
}
rt._renderedImage++;
};
/**
* Render RenderedTexture-Pass
*
* @param gl
* @param viewarea
* @param rt
*/
Context.prototype.renderRTPass = function ( gl, viewarea, rt )
{
/// begin special case (progressive image refinement)
if ( x3dom.isa( rt, x3dom.nodeTypes.RefinementTexture ) )
{
if ( rt.requirePingPong() )
{
this.renderPingPongPass( gl, viewarea, rt );
}
return;
}
/// end special case
switch ( rt._vf.update.toUpperCase() )
{
case "NONE":
return;
case "NEXT_FRAME_ONLY":
if ( !rt._needRenderUpdate )
{
return;
}
rt._needRenderUpdate = false;
break;
case "ALWAYS":
default:
break;
}
var scene = viewarea._scene;
var bgnd = null;
var mat_view = rt.getViewMatrix();
var mat_proj = rt.getProjectionMatrix();
var mat_scene = mat_proj.mult( mat_view );
var lightMatrix = viewarea.getLightMatrix()[ 0 ];
var mat_light = viewarea.getWCtoLCMatrix( lightMatrix );
var i,
n,
ex = rt._cf.excludeNodes.nodes,
m = ex.length;
var arr = new Array( m );
for ( i = 0; i < m; i++ )
{
var render = ex[ i ].renderFlag && ex[ i ].renderFlag();
if ( render === undefined )
{
arr[ i ] = -1;
}
else
{
if ( render === true )
{
arr[ i ] = 1;
}
else
{
arr[ i ] = 0;
}
}
rt._cf.excludeNodes.nodes[ i ]._vf.visible = false;
}
this.stateManager.bindFramebuffer( gl.FRAMEBUFFER, rt._webgl.fbo.fbo );
this.stateManager.viewport( 0, 0, rt._webgl.fbo.width, rt._webgl.fbo.height );
if ( rt._cf.background.node === null )
{
gl.clearColor( 0, 0, 0, 1 );
gl.clearDepth( 1.0 );
gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT );
}
else if ( rt._cf.background.node === scene.getBackground() )
{
bgnd = scene.getBackground();
bgnd._webgl.render( gl, mat_view, mat_proj, viewarea );
}
else
{
bgnd = rt._cf.background.node;
this.setupScene( gl, bgnd );
bgnd._webgl.render( gl, mat_view, mat_proj, viewarea );
}
this.stateManager.depthFunc( gl.LEQUAL );
this.stateManager.enable( gl.DEPTH_TEST );
this.stateManager.enable( gl.CULL_FACE );
this.stateManager.blendFuncSeparate( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE );
this.stateManager.enable( gl.BLEND );
var slights = viewarea.getLights(),
numLights = slights.length,
transform,
shape,
drawable;
var locScene = rt._cf.scene.node;
if ( !locScene || locScene === scene )
{
n = scene.drawableCollection.length;
if ( rt._vf.showNormals )
{
this.renderNormals( gl, scene, scene._webgl.normalShader, mat_view, mat_scene );
}
else
{
this.stateManager.unsetProgram();
for ( i = 0; i < n; i++ )
{
drawable = scene.drawableCollection.get( i );
this.renderShape( drawable, viewarea, slights, numLights,
mat_view, mat_scene, mat_light, mat_proj, gl );
}
}
}
else
{
var env = scene.getEnvironment();
var drawableCollectionConfig = {
viewArea : viewarea,
sortTrans : env._vf.sortTrans,
viewMatrix : mat_view,
projMatrix : mat_proj,
sceneMatrix : mat_scene,
frustumCulling : false,
smallFeatureThreshold : 1,
context : this,
gl : gl
};
locScene.numberOfNodes = 0;
locScene.drawableCollection = new x3dom.DrawableCollection( drawableCollectionConfig );
locScene.collectDrawableObjects( x3dom.fields.SFMatrix4f.identity(),
locScene.drawableCollection, true, false, 0, [] );
locScene.drawableCollection.sort();
n = locScene.drawableCollection.length;
if ( rt._vf.showNormals )
{
this.renderNormals( gl, locScene, scene._webgl.normalShader, mat_view, mat_scene );
}
else
{
this.stateManager.unsetProgram();
for ( i = 0; i < n; i++ )
{
drawable = locScene.drawableCollection.get( i );
if ( !drawable.shape.renderFlag() )
{
continue;
}
this.renderShape( drawable, viewarea, slights, numLights,
mat_view, mat_scene, mat_light, mat_proj, gl );
}
}
}
this.stateManager.disable( gl.BLEND );
this.stateManager.disable( gl.DEPTH_TEST );
gl.flush();
this.stateManager.bindFramebuffer( gl.FRAMEBUFFER, null );
if ( rt._webgl.fbo.mipMap )
{
gl.bindTexture( gl.TEXTURE_2D, rt._webgl.fbo.tex );
gl.generateMipmap( gl.TEXTURE_2D );
gl.bindTexture( gl.TEXTURE_2D, null );
}
for ( i = 0; i < m; i++ )
{
if ( arr[ i ] !== 0 )
{
rt._cf.excludeNodes.nodes[ i ]._vf.visible = true;
}
}
};
/**
* Render Normals
*
* @param gl
* @param scene
* @param sp
* @param mat_view
* @param mat_scene
*/
Context.prototype.renderNormals = function ( gl, scene, sp, mat_view, mat_scene )
{
if ( !sp || !scene )
{ // error
return;
}
this.stateManager.depthFunc( gl.LEQUAL );
this.stateManager.enable( gl.DEPTH_TEST );
this.stateManager.enable( gl.CULL_FACE );
this.stateManager.disable( gl.BLEND );
this.stateManager.useProgram( sp );
var bgCenter = x3dom.fields.SFVec3f.NullVector.toGL();
var bgSize = x3dom.fields.SFVec3f.OneVector.toGL();
for ( var i = 0, n = scene.drawableCollection.length; i < n; i++ )
{
var drawable = scene.drawableCollection.get( i );
var trafo = drawable.transform;
var shape = drawable.shape;
var s_gl = shape._webgl;
if ( !s_gl || !shape || !shape.renderFlag() )
{
continue;
}
var s_geo = shape._cf.geometry.node;
var s_msh = s_geo._mesh;
var model_view_inv = mat_view.mult( trafo ).inverse();
sp.normalMatrix = model_view_inv.transpose().toGL();
sp.modelViewProjectionMatrix = mat_scene.mult( trafo ).toGL();
if ( s_gl.coordType != gl.FLOAT )
{
if ( s_gl.popGeometry != 0 ||
( s_msh._numPosComponents == 4 && x3dom.Utils.isUnsignedType( s_geo._vf.coordType ) ) )
{
sp.bgCenter = s_geo.getMin().toGL();
}
else
{
sp.bgCenter = s_geo._vf.position.toGL();
}
sp.bgSize = s_geo._vf.size.toGL();
sp.bgPrecisionMax = s_geo.getPrecisionMax( "coordType" );
}
else
{
sp.bgCenter = bgCenter;
sp.bgSize = bgSize;
sp.bgPrecisionMax = 1;
}
if ( s_gl.normalType != gl.FLOAT )
{
sp.bgPrecisionNorMax = s_geo.getPrecisionMax( "normalType" );
}
else
{
sp.bgPrecisionNorMax = 1;
}
if ( shape.isSolid() )
{
this.stateManager.enable( gl.CULL_FACE );
if ( shape.isCCW() )
{
this.stateManager.frontFace( gl.CCW );
}
else
{
this.stateManager.frontFace( gl.CW );
}
}
else
{
this.stateManager.disable( gl.CULL_FACE );
}
// render shape
for ( var q = 0, q_n = s_gl.positions.length; q < q_n; q++ )
{
var q6 = 6 * q;
var v,
v_n,
offset;
if ( !( sp.position !== undefined && s_gl.buffers[ q6 + x3dom.BUFFER_IDX.POSITION ] && s_gl.indexes[ q ] ) )
{continue;}
// bind buffers
if ( s_gl.buffers[ q6 + x3dom.BUFFER_IDX.INDEX ] )
{
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, s_gl.buffers[ q6 + x3dom.BUFFER_IDX.INDEX ] );
}
this.setVertexAttribPointerPosition( gl, shape, q6, q );
this.setVertexAttribPointerNormal( gl, shape, q6, q );
// draw mesh
if ( s_gl.binaryGeometry > 0 || s_gl.popGeometry > 0 )
{
for ( v = 0, offset = 0, v_n = s_geo._vf.vertexCount.length; v < v_n; v++ )
{
this.drawElements( gl, s_gl.primType[ v ], s_geo._vf.vertexCount[ v ], s_gl.indexType,
x3dom.Utils.getByteAwareOffset( offset, s_gl.indexType, gl ) );
offset += s_geo._vf.vertexCount[ v ];
}
}
else if ( s_gl.binaryGeometry < 0 || s_gl.popGeometry < 0 )
{
for ( v = 0, offset = 0, v_n = s_geo._vf.vertexCount.length; v < v_n; v++ )
{
this.drawArrays( gl, s_gl.primType[ v ], offset, s_geo._vf.vertexCount[ v ] );
offset += s_geo._vf.vertexCount[ v ];
}
}
else if ( indicesReady && ( s_gl.bufferGeometry > 0 ) )
{
this.drawElements( gl, s_gl.primType[ 0 ], s_geo._vf.vertexCount[ 0 ], s_gl.indexType, shape._indexOffset );
}
else if ( s_gl.bufferGeometry < 0 )
{
this.drawArrays( gl, s_gl.primType[ 0 ], 0, s_geo._vf.vertexCount[ 0 ] );
}
else if ( s_geo.hasIndexOffset() )
{
var indOff = shape.tessellationProperties();
for ( v = 0, v_n = indOff.length; v < v_n; v++ )
{
this.drawElements( gl, s_gl.primType, indOff[ v ].count, s_gl.indexType,
indOff[ v ].offset * x3dom.Utils.getOffsetMultiplier( s_gl.indexType, gl ) );
}
}
else if ( s_gl.indexes[ q ].length == 0 )
{
this.drawArrays( gl, s_gl.primType, 0, s_gl.positions[ q ].length / 3 );
}
else
{
this.drawElements( gl, s_gl.primType, s_gl.indexes[ q ].length, s_gl.indexType, 0 );
}
gl.disableVertexAttribArray( sp.position );
if ( sp.normal !== undefined )
{
gl.disableVertexAttribArray( sp.normal );
}
}
}
};
/**
* Cleanup
*
* @param viewarea
*/
Context.prototype.shutdown = function ( viewarea )
{
var gl = this.ctx3d;
var scene = viewarea._scene;
if ( gl == null || !scene )
{
return;
}
var bgnd = scene.getBackground();
if ( bgnd._webgl && bgnd._webgl.position !== undefined )
{
gl.deleteBuffer( bgnd._webgl.buffers[ x3dom.BUFFER_IDX.INDEX ] );
gl.deleteBuffer( bgnd._webgl.buffers[ x3dom.BUFFER_IDX.POSITION ] );
}
var fgnd = scene._fgnd;
if ( fgnd._webgl.position !== undefined )
{
gl.deleteBuffer( fgnd._webgl.buffers[ x3dom.BUFFER_IDX.INDEX ] );
gl.deleteBuffer( fgnd._webgl.buffers[ x3dom.BUFFER_IDX.POSITION ] );
}
var n = scene.drawableCollection ? scene.drawableCollection.length : 0;
for ( var i = 0; i < n; i++ )
{
var shape = scene.drawableCollection.get( i ).shape;
if ( shape._cleanupGLObjects )
{shape._cleanupGLObjects( true );}
}
// Release Texture and Shader Resources
this.cache.Release( gl );
};
/**
* Draw shadows on screen
*
* @param gl
* @param viewarea
* @param shadowedLights
* @param wctolc
* @param lMatrices
* @param mat_view
* @param mat_proj
* @param mat_scene
*/
Context.prototype.renderShadows = function ( gl, viewarea, shadowedLights, wctolc, lMatrices,
mat_view, mat_proj, mat_scene )
{
var scene = viewarea._scene;
// don't render shadows with less than 7 textures per fragment shader
var texLimit = x3dom.caps.MAX_TEXTURE_IMAGE_UNITS;
if ( texLimit < 7 )
{return;}
var texUnits = 1;
var renderSplit = [ 0 ];
var shadowMaps,
numShadowMaps,
i,
j,
k;
// filter shadow maps and determine, if multiple render passes are needed
for ( i = 0; i < shadowedLights.length; i++ )
{
var filterSize = shadowedLights[ i ]._vf.shadowFilterSize;
shadowMaps = scene._webgl.fboShadow[ i ];
numShadowMaps = shadowMaps.length;
// filtering
for ( j = 0; j < numShadowMaps; j++ )
{
this.blurTex( gl, scene, shadowMaps[ j ], filterSize );
}
// shader consumes 6 tex units per lights (even if less are bound)
texUnits += 6;
if ( texUnits > texLimit )
{
renderSplit[ renderSplit.length ] = i;
texUnits = 7;
}
}
renderSplit[ renderSplit.length ] = shadowedLights.length;
// render shadows for current render split
var n = renderSplit.length - 1;
var mat_proj_inv = mat_proj.inverse();
var mat_scene_inv = mat_scene.inverse();
// enable (multiplicative) blending
this.stateManager.enable( gl.BLEND );
this.stateManager.blendFunc( gl.DST_COLOR, gl.ZERO );
for ( var s = 0; s < n; s++ )
{
var startIndex = renderSplit[ s ];
var endIndex = renderSplit[ s + 1 ];
var currentLights = [];
for ( k = startIndex; k < endIndex; k++ )
{currentLights[ currentLights.length ] = shadowedLights[ k ];}
// generate shadow shader properties
var properties = {};
properties.FOG = ( scene.getFog()._vf.visibilityRange > 0 ) ? 1 : 0;
var sp = this.cache.getShadowRenderingShader( gl, currentLights, properties );
this.stateManager.useProgram( sp );
gl.bindBuffer( gl.ARRAY_BUFFER, scene._webgl.ppBuffer );
gl.vertexAttribPointer( sp.position, 2, gl.FLOAT, false, 0, 0 );
gl.enableVertexAttribArray( sp.position );
// bind depth texture (depth from camera view)
sp.sceneMap = 0;
gl.activeTexture( gl.TEXTURE0 );
gl.bindTexture( gl.TEXTURE_2D, scene._webgl.fboScene.tex );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE );
// compute inverse projection matrix
sp.inverseProj = mat_proj_inv.toGL();
// compute inverse view projection matrix
sp.inverseViewProj = mat_scene_inv.toGL();
var mat_light,
lightMatrix;
var shadowIndex = 0;
for ( var p = 0, pn = currentLights.length; p < pn; p++ )
{
// get light matrices and shadow maps for current light
lightMatrix = lMatrices[ p + startIndex ];
mat_light = wctolc[ p + startIndex ];
shadowMaps = scene._webgl.fboShadow[ p + startIndex ];
numShadowMaps = mat_light.length;
for ( i = 0; i < numShadowMaps; i++ )
{
gl.activeTexture( gl.TEXTURE1 + shadowIndex );
gl.bindTexture( gl.TEXTURE_2D, shadowMaps[ i ].tex );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE );
sp[ "light" + p + "_" + i + "_ShadowMap" ] = shadowIndex + 1;
sp[ "light" + p + "_" + i + "_Matrix" ] = mat_light[ i ].toGL();
shadowIndex++;
}
sp[ "light" + p + "_ViewMatrix" ] = lightMatrix.toGL();
// cascade depths for directional and spot light
if ( !x3dom.isa( currentLights[ p ], x3dom.nodeTypes.PointLight ) )
{
for ( j = 0; j < numShadowMaps; j++ )
{
var numCascades = Math.max( 1, Math.min( currentLights[ p ]._vf.shadowCascades, 6 ) );
var splitFactor = Math.max( 0, Math.min( currentLights[ p ]._vf.shadowSplitFactor, 1 ) );
var splitOffset = Math.max( 0, Math.min( currentLights[ p ]._vf.shadowSplitOffset, 1 ) );
var splitDepths = viewarea.getShadowSplitDepths( numCascades, splitFactor, splitOffset, false, mat_proj );
sp[ "light" + p + "_" + j + "_Split" ] = splitDepths[ j + 1 ];
}
}
// assign light properties
var light_transform = mat_view.mult( currentLights[ p ].getCurrentTransform() );
if ( x3dom.isa( currentLights[ p ], x3dom.nodeTypes.DirectionalLight ) )
{
sp[ "light" + p + "_Type" ] = 0.0;
sp[ "light" + p + "_On" ] = ( currentLights[ p ]._vf.on ) ? 1.0 : 0.0;
sp[ "light" + p + "_Direction" ] = light_transform.multMatrixVec( currentLights[ p ]._vf.direction ).toGL();
sp[ "light" + p + "_Attenuation" ] = [ 1.0, 1.0, 1.0 ];
sp[ "light" + p + "_Location" ] = [ 1.0, 1.0, 1.0 ];
sp[ "light" + p + "_Radius" ] = 0.0;
sp[ "light" + p + "_BeamWidth" ] = 0.0;
sp[ "light" + p + "_CutOffAngle" ] = 0.0;
sp[ "light" + p + "_ShadowIntensity" ] = currentLights[ p ]._vf.shadowIntensity;
sp[ "light" + p + "_ShadowCascades" ] = currentLights[ p ]._vf.shadowCascades;
sp[ "light" + p + "_ShadowOffset" ] = Math.max( 0.0, Math.min( 1.0, currentLights[ p ]._vf.shadowOffset ) );
}
else if ( x3dom.isa( currentLights[ p ], x3dom.nodeTypes.PointLight ) )
{
sp[ "light" + p + "_Type" ] = 1.0;
sp[ "light" + p + "_On" ] = ( currentLights[ p ]._vf.on ) ? 1.0 : 0.0;
sp[ "light" + p + "_Direction" ] = [ 1.0, 1.0, 1.0 ];
sp[ "light" + p + "_Attenuation" ] = currentLights[ p ]._vf.attenuation.toGL();
sp[ "light" + p + "_Location" ] = light_transform.multMatrixPnt( currentLights[ p ]._vf.location ).toGL();
sp[ "light" + p + "_Radius" ] = currentLights[ p ]._vf.radius;
sp[ "light" + p + "_BeamWidth" ] = 0.0;
sp[ "light" + p + "_CutOffAngle" ] = 0.0;
sp[ "light" + p + "_ShadowIntensity" ] = currentLights[ p ]._vf.shadowIntensity;
sp[ "light" + p + "_ShadowOffset" ] = Math.max( 0.0, Math.min( 1.0, currentLights[ p ]._vf.shadowOffset ) );
}
else if ( x3dom.isa( currentLights[ p ], x3dom.nodeTypes.SpotLight ) )
{
sp[ "light" + p + "_Type" ] = 2.0;
sp[ "light" + p + "_On" ] = ( currentLights[ p ]._vf.on ) ? 1.0 : 0.0;
sp[ "light" + p + "_Direction" ] = light_transform.multMatrixVec( currentLights[ p ]._vf.direction ).toGL();
sp[ "light" + p + "_Attenuation" ] = currentLights[ p ]._vf.attenuation.toGL();
sp[ "light" + p + "_Location" ] = light_transform.multMatrixPnt( currentLights[ p ]._vf.location ).toGL();
sp[ "light" + p + "_Radius" ] = currentLights[ p ]._vf.radius;
sp[ "light" + p + "_BeamWidth" ] = currentLights[ p ]._vf.beamWidth;
sp[ "light" + p + "_CutOffAngle" ] = currentLights[ p ]._vf.cutOffAngle;
sp[ "light" + p + "_ShadowIntensity" ] = currentLights[ p ]._vf.shadowIntensity;
sp[ "light" + p + "_ShadowCascades" ] = currentLights[ p ]._vf.shadowCascades;
sp[ "light" + p + "_ShadowOffset" ] = Math.max( 0.0, Math.min( 1.0, currentLights[ p ]._vf.shadowOffset ) );
}
}
if ( properties.FOG ) { sp.fogType = 999.0; } // draw without fog first
gl.drawArrays( gl.TRIANGLES, 0, 6 ); //shadows
// Set fog
if ( properties.FOG )
{
var fog = scene.getFog();
this.stateManager.blendColor( fog._vf.color.r, fog._vf.color.g, fog._vf.color.b, 1.0 );
this.stateManager.blendFunc( gl.CONSTANT_COLOR, gl.ONE_MINUS_SRC_COLOR );
sp.fogColor = fog._vf.color.toGL();
sp.fogRange = fog._vf.visibilityRange;
sp.fogType = ( fog._vf.fogType == "LINEAR" ) ? 0.0 : 1.0;
gl.drawArrays( gl.TRIANGLES, 0, 6 ); // fog
}
// cleanup
var nk = shadowIndex + 1;
for ( k = 0; k < nk; k++ )
{
gl.activeTexture( gl.TEXTURE0 + k );
gl.bindTexture( gl.TEXTURE_2D, null );
}
gl.disableVertexAttribArray( sp.position );
}
this.stateManager.blendFunc( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA );
};
/**
* Blur texture associated with given fbo
*
* @param gl
* @param scene
* @param targetFbo
* @param filterSize
*/
Context.prototype.blurTex = function ( gl, scene, targetFbo, filterSize )
{
if ( filterSize <= 0 )
{
return;
}
else if ( filterSize < 5 )
{
filterSize = 3;
}
else if ( filterSize < 7 )
{
filterSize = 5;
}
else
{
filterSize = 7;
}
// first pass (horizontal blur), result stored in fboBlur
var width = targetFbo.width;
var height = targetFbo.height;
var fboBlur = null;
for ( var i = 0, n = scene._webgl.fboBlur.length; i < n; i++ )
{
if ( height == scene._webgl.fboBlur[ i ].height )
{
fboBlur = scene._webgl.fboBlur[ i ];
break; // THINKABOUTME
}
}
this.stateManager.bindFramebuffer( gl.FRAMEBUFFER, fboBlur.fbo );
this.stateManager.viewport( 0, 0, width, height );
this.stateManager.enable( gl.BLEND );
this.stateManager.blendFunc( gl.ONE, gl.ZERO );
this.stateManager.disable( gl.CULL_FACE );
this.stateManager.disable( gl.DEPTH_TEST );
gl.clearColor( 1.0, 1.0, 1.0, 0.0 );
gl.clearDepth( 1.0 );
gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
var sp = this.cache.getShader( gl, x3dom.shader.BLUR );
this.stateManager.useProgram( sp );
// initialize Data for post processing
gl.bindBuffer( gl.ARRAY_BUFFER, scene._webgl.ppBuffer );
gl.vertexAttribPointer( sp.position, 2, gl.FLOAT, false, 0, 0 );
gl.enableVertexAttribArray( sp.position );
sp.pixelSizeHor = 1.0 / width;
sp.pixelSizeVert = 1.0 / height;
sp.filterSize = filterSize;
sp.horizontal = true;
sp.texture = 0;
// bind texture
gl.activeTexture( gl.TEXTURE0 );
gl.bindTexture( gl.TEXTURE_2D, targetFbo.tex );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE );
gl.drawArrays( gl.TRIANGLES, 0, 6 );
// second pass (vertical blur), result stored in targetFbo
this.stateManager.bindFramebuffer( gl.FRAMEBUFFER, targetFbo.fbo );
gl.clearColor( 1.0, 1.0, 1.0, 0.0 );
gl.clearDepth( 1.0 );
gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
sp.horizontal = false;
gl.activeTexture( gl.TEXTURE0 );
gl.bindTexture( gl.TEXTURE_2D, fboBlur.tex );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE );
gl.drawArrays( gl.TRIANGLES, 0, 6 );
// cleanup
gl.activeTexture( gl.TEXTURE0 );
gl.bindTexture( gl.TEXTURE_2D, null );
gl.disableVertexAttribArray( sp.position );
gl.flush();
this.stateManager.blendFunc( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA );
this.stateManager.bindFramebuffer( gl.FRAMEBUFFER, null );
this.stateManager.viewport( 0, 0, this.canvas.width, this.canvas.height );
};
Context.prototype.drawElements = function ( gl, mode, count, type, offset, instanceCount )
{
instanceCount = ( instanceCount == undefined ) ? 1 : instanceCount;
instanceCount *= this.VRMode;
if ( x3dom.caps.WEBGL_VERSION == 2 )
{
gl.drawElementsInstanced( mode, count, type, offset, instanceCount );
}
else if ( x3dom.caps.INSTANCED_ARRAYS )
{
var instancedArrays = this.ctx3d.getExtension( "ANGLE_instanced_arrays" );
instancedArrays.drawElementsInstancedANGLE( mode, count, type, offset, instanceCount );
}
else
{
gl.drawElements( mode, count, type, offset );
}
};
Context.prototype.drawArrays = function ( gl, mode, first, count, instanceCount )
{
instanceCount = ( instanceCount == undefined ) ? 1 : instanceCount;
instanceCount *= this.VRMode;
if ( x3dom.caps.WEBGL_VERSION == 2 )
{
gl.drawArraysInstanced( mode, first, count, instanceCount );
}
else if ( x3dom.caps.INSTANCED_ARRAYS )
{
var instancedArrays = this.ctx3d.getExtension( "ANGLE_instanced_arrays" );
instancedArrays.drawArraysInstancedANGLE( mode, first, count, instanceCount );
}
else
{
gl.drawArrays( mode, first, count );
}
};
Context.prototype.setVertexAttribEyeIdx = function ( gl, sp )
{
if ( x3dom.caps.WEBGL_VERSION == 2 && sp.eyeIdx != undefined )
{
if ( !this.eyeIdxBuffer )
{
this.eyeIdxBuffer = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER, this.eyeIdxBuffer );
gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( [ -1.0, 1.0 ] ), gl.STATIC_DRAW );
gl.vertexAttribPointer( sp.eyeIdx, 1, gl.FLOAT, false, 0, 0 );
gl.enableVertexAttribArray( sp.eyeIdx );
gl.vertexAttribDivisor( sp.eyeIdx, 1 );
}
else
{
gl.bindBuffer( gl.ARRAY_BUFFER, this.eyeIdxBuffer );
gl.vertexAttribPointer( sp.eyeIdx, 1, gl.FLOAT, false, 0, 0 );
gl.enableVertexAttribArray( sp.eyeIdx );
gl.vertexAttribDivisor( sp.eyeIdx, 1 );
}
}
else if ( x3dom.caps.INSTANCED_ARRAYS && sp.eyeIdx != undefined )
{
var instancedArrays = this.ctx3d.getExtension( "ANGLE_instanced_arrays" );
if ( !this.eyeIdxBuffer )
{
this.eyeIdxBuffer = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER, this.eyeIdxBuffer );
gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( [ -1.0, 1.0 ] ), gl.STATIC_DRAW );
gl.vertexAttribPointer( sp.eyeIdx, 1, gl.FLOAT, false, 0, 0 );
gl.enableVertexAttribArray( sp.eyeIdx );
instancedArrays.vertexAttribDivisorANGLE( sp.eyeIdx, 1 );
}
else
{
gl.bindBuffer( gl.ARRAY_BUFFER, this.eyeIdxBuffer );
gl.vertexAttribPointer( sp.eyeIdx, 1, gl.FLOAT, false, 0, 0 );
gl.enableVertexAttribArray( sp.eyeIdx );
instancedArrays.vertexAttribDivisorANGLE( sp.eyeIdx, 1 );
}
}
};
Context.prototype.disableVertexAttribEyeIdx = function ( gl, sp )
{
if ( x3dom.caps.WEBGL_VERSION == 2 && sp.eyeIdx != undefined )
{
gl.disableVertexAttribArray( sp.eyeIdx );
gl.vertexAttribDivisor( sp.eyeIdx, 0 );
}
else if ( x3dom.caps.INSTANCED_ARRAYS && sp.eyeIdx != undefined )
{
var instancedArrays = this.ctx3d.getExtension( "ANGLE_instanced_arrays" );
gl.disableVertexAttribArray( sp.eyeIdx );
instancedArrays.vertexAttribDivisorANGLE( sp.eyeIdx, 0 );
}
};
/**
* Set Vertex Attrib Pointer Position
*
* @param gl
* @param shape
* @param q6
* @param q
*/
Context.prototype.setVertexAttribPointerPosition = function ( gl, shape, q6, q )
{
var sp = shape._webgl.shader;
if ( sp.position !== undefined && shape._webgl.buffers[ q6 + x3dom.BUFFER_IDX.POSITION ] )
{
var s_geo = shape._cf.geometry.node;
gl.bindBuffer( gl.ARRAY_BUFFER, shape._webgl.buffers[ q6 + x3dom.BUFFER_IDX.POSITION ] );
gl.vertexAttribPointer( sp.position,
s_geo._mesh._numPosComponents, shape._webgl.coordType, shape._webgl.coordNormalized,
shape._coordStrideOffset[ 0 ], shape._coordStrideOffset[ 1 ] );
gl.enableVertexAttribArray( sp.position );
}
};
Context.prototype.setVertexAttribPointerNormal = function ( gl, shape, q6, q )
{
var sp = shape._webgl.shader;
if ( sp.normal !== undefined && shape._webgl.buffers[ q6 + x3dom.BUFFER_IDX.NORMAL ] )
{
var s_geo = shape._cf.geometry.node;
gl.bindBuffer( gl.ARRAY_BUFFER, shape._webgl.buffers[ q6 + x3dom.BUFFER_IDX.NORMAL ] );
gl.vertexAttribPointer( sp.normal,
s_geo._mesh._numNormComponents, shape._webgl.normalType, shape._webgl.normalNormalized,
shape._normalStrideOffset[ 0 ], shape._normalStrideOffset[ 1 ] );
gl.enableVertexAttribArray( sp.normal );
}
};
/**
* Set Vertex Attrib Pointer Tex Coord
*
* @param gl
* @param shape
* @param q6
* @param q
*/
Context.prototype.setVertexAttribPointerTexCoord = function ( gl, shape, q6, q )
{
var sp = shape._webgl.shader;
if ( sp.texcoord !== undefined && shape._webgl.buffers[ q6 + x3dom.BUFFER_IDX.TEXCOORD ] )
{
var s_geo = shape._cf.geometry.node;
gl.bindBuffer( gl.ARRAY_BUFFER, shape._webgl.buffers[ q6 + x3dom.BUFFER_IDX.TEXCOORD ] );
gl.vertexAttribPointer( sp.texcoord,
s_geo._mesh._numTexComponents, shape._webgl.texCoordType, shape._webgl.texCoordNormalized,
shape._texCoordStrideOffset[ 0 ], shape._texCoordStrideOffset[ 1 ] );
gl.enableVertexAttribArray( sp.texcoord );
}
};
/**
* Set Vertex Attrib Pointer Tex Coord 2
*
* @param gl
* @param shape
* @param q6
* @param q
*/
Context.prototype.setVertexAttribPointerTexCoord2 = function ( gl, shape, q6, q )
{
var sp = shape._webgl.shader;
if ( sp.texcoord2 !== undefined && shape._webgl.buffers[ q6 + x3dom.BUFFER_IDX.TEXCOORD_1 ] )
{
var s_geo = shape._cf.geometry.node;
gl.bindBuffer( gl.ARRAY_BUFFER, shape._webgl.buffers[ q6 + x3dom.BUFFER_IDX.TEXCOORD_1 ] );
gl.vertexAttribPointer( sp.texcoord2,
s_geo._mesh._numTex2Components, shape._webgl.texCoord2Type, shape._webgl.texCoord2Normalized,
shape._texCoord2StrideOffset[ 0 ], shape._texCoord2StrideOffset[ 1 ] );
gl.enableVertexAttribArray( sp.texcoord2 );
}
};
/**
* Set Vertex Attrib Pointer Color
*
* @param gl
* @param shape
* @param q6
* @param q
*/
Context.prototype.setVertexAttribPointerColor = function ( gl, shape, q6, q )
{
var sp = shape._webgl.shader;
if ( sp.color !== undefined && shape._webgl.buffers[ q6 + x3dom.BUFFER_IDX.COLOR ] )
{
var s_geo = shape._cf.geometry.node;
gl.bindBuffer( gl.ARRAY_BUFFER, shape._webgl.buffers[ q6 + x3dom.BUFFER_IDX.COLOR ] );
gl.vertexAttribPointer( sp.color,
s_geo._mesh._numColComponents, shape._webgl.colorType, shape._webgl.colorNormalized,
shape._colorStrideOffset[ 0 ], shape._colorStrideOffset[ 1 ] );
gl.enableVertexAttribArray( sp.color );
}
};
Context.prototype.setVertexAttribPointerTangent = function ( gl, shape, q6, q )
{
var sp = shape._webgl.shader;
if ( sp.tangent !== undefined && shape._webgl.buffers[ q6 + x3dom.BUFFER_IDX.TANGENT ] )
{
var s_geo = shape._cf.geometry.node;
gl.bindBuffer( gl.ARRAY_BUFFER, shape._webgl.buffers[ q6 + x3dom.BUFFER_IDX.TANGENT ] );
gl.vertexAttribPointer( sp.tangent,
s_geo._mesh._numTangentComponents, shape._webgl.tangentType, shape._webgl.tangentNormalized,
shape._tangentStrideOffset[ 0 ], shape._tangentStrideOffset[ 1 ] );
gl.enableVertexAttribArray( sp.tangent );
}
};
Context.prototype.setVertexAttribPointerBinormal = function ( gl, shape, q6, q )
{
var sp = shape._webgl.shader;
if ( sp.binormal !== undefined && shape._webgl.buffers[ q6 + x3dom.BUFFER_IDX.BITANGENT ] )
{
var s_geo = shape._cf.geometry.node;
gl.bindBuffer( gl.ARRAY_BUFFER, shape._webgl.buffers[ q6 + x3dom.BUFFER_IDX.BITANGENT ] );
gl.vertexAttribPointer( sp.binormal,
s_geo._mesh._numBinormalComponents, shape._webgl.binormalType, shape._webgl.binormalNormalized,
shape._binormalStrideOffset[ 0 ], shape._binormalStrideOffset[ 1 ] );
gl.enableVertexAttribArray( sp.binormal );
}
};
Context.prototype.setTonemappingOperator = function ( viewarea, sp )
{
var scene = viewarea._scene;
var env = scene.getEnvironment();
switch ( env._vf.tonemapping )
{
case "none":
sp.tonemappingOperator = 0.0;
break;
case "reinhard":
sp.tonemappingOperator = 1.0;
break;
case "uncharted":
sp.tonemappingOperator = 2.0;
break;
case "filmic":
sp.tonemappingOperator = 3.0;
break;
default:
sp.tonemappingOperator = 0.0;
break;
}
};
return setupContext;
} )();
/**
* X3DOM JavaScript Library
* http://www.x3dom.org
*
* (C)2009 Fraunhofer IGD, Darmstadt, Germany
* Dual licensed under the MIT and GPL
*
* Based on code originally provided by
* Philip Taylor: http://philip.html5.org
*/
x3dom.SSAO = {};
/**
* Is Enabled?
*
* @param scene
* @returns {{}|x3dom.fields.SFBool|string}
*/
x3dom.SSAO.isEnabled = function ( scene )
{
return scene.getEnvironment()._vf.SSAO;
};
/**
* Reinitializes the shaders if they were not created yet or need to be updated.
*
* @param gl
*/
x3dom.SSAO.reinitializeShadersIfNecessary = function ( gl )
{
if ( x3dom.SSAO.shaderProgram === undefined )
{
x3dom.SSAO.shaderProgram = x3dom.Utils.wrapProgram( gl, new x3dom.shader.SSAOShader( gl ), "ssao" );
}
if ( x3dom.SSAO.blurShaderProgram === undefined )
{
x3dom.SSAO.blurShaderProgram = x3dom.Utils.wrapProgram( gl, new x3dom.shader.SSAOBlurShader( gl ), "ssao-blur" );
}
};
/**
* Reinitializes the random Texture if it was not created yet or if it's size changed.
*
* @param gl
* @param scene
*/
x3dom.SSAO.reinitializeRandomTextureIfNecessary = function ( gl, scene )
{
var sizeHasChanged = scene.getEnvironment()._vf.SSAOrandomTextureSize != x3dom.SSAO.currentRandomTextureSize;
if ( x3dom.SSAO.randomTexture === undefined )
{
// create random texture
x3dom.SSAO.randomTexture = gl.createTexture();
}
if ( x3dom.SSAO.randomTexture === undefined || sizeHasChanged )
{
gl.bindTexture( gl.TEXTURE_2D, x3dom.SSAO.randomTexture );
var rTexSize = x3dom.SSAO.currentRandomTextureSize = scene.getEnvironment()._vf.SSAOrandomTextureSize;
var randomImageBuffer = new ArrayBuffer( rTexSize * rTexSize * 4 ); //rTexSize^2 pixels with 4 bytes each
var randomImageView = new Uint8Array( randomImageBuffer );
// fill the image with random unit Vectors in the z-y-plane:
for ( var i = 0; i < rTexSize * rTexSize; ++i )
{
var x = Math.random() * 2.0 - 1.0;
var y = Math.random() * 2.0 - 1.0;
var z = 0;
var length = Math.sqrt( x * x + y * y + z * z );
x /= length;
y /= length;
randomImageView[ 4 * i ] = ( x + 1.0 ) * 0.5 * 255.0;
randomImageView[ 4 * i + 1 ] = ( y + 1.0 ) * 0.5 * 255.0;
randomImageView[ 4 * i + 2 ] = ( z + 1.0 ) * 0.5 * 255.0;
randomImageView[ 4 * i + 3 ] = 255;
}
gl.texImage2D( gl.TEXTURE_2D, 0, gl.RGBA, rTexSize, rTexSize, 0, gl.RGBA, gl.UNSIGNED_BYTE, randomImageView );
gl.bindTexture( gl.TEXTURE_2D, null );
}
};
/**
* Reinitializes the FBO render target for the SSAO if it wasn't created yet or if the canvas size changed.
*
* @param gl
* @param canvas
*/
x3dom.SSAO.reinitializeFBOIfNecessary = function ( gl, canvas )
{
var dimensionsHaveChanged =
x3dom.SSAO.currentFBOWidth != canvas.width ||
x3dom.SSAO.currentFBOHeight != canvas.height;
if ( x3dom.SSAO.fbo === undefined || dimensionsHaveChanged )
{
x3dom.SSAO.currentFBOWidth = canvas.width;
x3dom.SSAO.currentFBOHeight = canvas.height;
var oldfbo = gl.getParameter( gl.FRAMEBUFFER_BINDING );
if ( x3dom.SSAO.fbo === undefined )
{
// create framebuffer
x3dom.SSAO.fbo = gl.createFramebuffer();
}
gl.bindFramebuffer( gl.FRAMEBUFFER, x3dom.SSAO.fbo );
if ( x3dom.SSAO.fbotex === undefined )
{
// create render texture
x3dom.SSAO.fbotex = gl.createTexture();
}
gl.bindTexture( gl.TEXTURE_2D, x3dom.SSAO.fbotex );
gl.texImage2D( gl.TEXTURE_2D, 0, gl.RGBA, x3dom.SSAO.currentFBOWidth,
x3dom.SSAO.currentFBOHeight, 0, gl.RGBA, gl.UNSIGNED_BYTE, null );
gl.bindTexture( gl.TEXTURE_2D, null );
gl.framebufferTexture2D( gl.FRAMEBUFFER,
gl.COLOR_ATTACHMENT0,
gl.TEXTURE_2D,
x3dom.SSAO.fbotex,
0 );
gl.bindFramebuffer( gl.FRAMEBUFFER, oldfbo );
}
};
/*
* Renders a sparsely sampled Screen-Space Ambient Occlusion Factor.
*
* @param stateManager - x3dom webgl stateManager
* @param gl - WebGL context
* @param scene - Scene Node
* @param tex - depth texture
* @param canvas - Canvas the scene is rendered on (needed for dimensions)
* @param fbo - FrameBufferObject handle that should be used as a target (null to use curent fbo)
*/
x3dom.SSAO.render = function ( stateManager, gl, scene, tex, canvas, fbo )
{
// save previous fbo
var oldfbo = gl.getParameter( gl.FRAMEBUFFER_BINDING );
if ( fbo != null )
{
gl.bindFramebuffer( gl.FRAMEBUFFER, fbo );
}
stateManager.frontFace( gl.CCW );
stateManager.disable( gl.CULL_FACE );
stateManager.disable( gl.DEPTH_TEST );
var sp = x3dom.SSAO.shaderProgram;
stateManager.useProgram( sp );
// set up uniforms
sp.depthTexture = 0;
sp.randomTexture = 1;
sp.radius = scene.getEnvironment()._vf.SSAOradius;
sp.randomTextureTilingFactor = [ canvas.width / x3dom.SSAO.currentRandomTextureSize, canvas.height / x3dom.SSAO.currentRandomTextureSize ];
var viewpoint = scene.getViewpoint();
var nearPlane = viewpoint.getNear();
var farPlane = viewpoint.getFar();
sp.nearPlane = nearPlane;
sp.farPlane = farPlane;
sp.depthReconstructionConstantA = ( farPlane + nearPlane ) / ( nearPlane - farPlane );
sp.depthReconstructionConstantB = ( 2.0 * farPlane * nearPlane ) / ( nearPlane - farPlane );
sp.depthBufferEpsilon = 0.0001 * ( farPlane - nearPlane );
// 16 samples with a well distributed pseudo random opposing-pairs sampling pattern:
sp.samples = [
0.03800223814729654, 0.10441029119843426, -0.04479934806797181,
-0.03800223814729654, -0.10441029119843426, 0.04479934806797181,
-0.17023209847088397, 0.1428416910414532, 0.6154407640895228,
0.17023209847088397, -0.1428416910414532, -0.6154407640895228,
-0.288675134594813, -0.16666666666666646, -0.3774214123135722,
0.288675134594813, 0.16666666666666646, 0.3774214123135722,
0.07717696785196887, -0.43769233467209245, -0.5201284112706428,
-0.07717696785196887, 0.43769233467209245, 0.5201284112706428,
0.5471154183401156, -0.09647120981496134, -0.15886420745887797,
-0.5471154183401156, 0.09647120981496134, 0.15886420745887797,
0.3333333333333342, 0.5773502691896253, -0.8012446019636266,
-0.3333333333333342, -0.5773502691896253, 0.8012446019636266,
-0.49994591864508653, 0.5958123446480936, -0.15385106176844343,
0.49994591864508653, -0.5958123446480936, 0.15385106176844343,
-0.8352823295874743, -0.3040179051783715, 0.7825440557226517,
0.8352823295874743, 0.3040179051783715, -0.7825440557226517
];
if ( !sp.tex )
{
sp.tex = 0;
}
// depth texture
gl.activeTexture( gl.TEXTURE0 );
gl.bindTexture( gl.TEXTURE_2D, tex );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE );
// random texture:
gl.activeTexture( gl.TEXTURE1 );
gl.bindTexture( gl.TEXTURE_2D, x3dom.SSAO.randomTexture );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT );
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, scene._fgnd._webgl.buffers[ x3dom.BUFFER_IDX.INDEX ] );
gl.bindBuffer( gl.ARRAY_BUFFER, scene._fgnd._webgl.buffers[ x3dom.BUFFER_IDX.POSITION ] );
gl.vertexAttribPointer( sp.position, 3, gl.FLOAT, false, 0, 0 );
gl.enableVertexAttribArray( sp.position );
gl.drawElements( scene._fgnd._webgl.primType, scene._fgnd._webgl.indexes.length, gl.UNSIGNED_SHORT, 0 );
gl.disableVertexAttribArray( sp.position );
gl.activeTexture( gl.TEXTURE0 );
gl.bindTexture( gl.TEXTURE_2D, null );
gl.activeTexture( gl.TEXTURE1 );
gl.bindTexture( gl.TEXTURE_2D, null );
// restore prevoius fbo
if ( fbo != null )
{
gl.bindFramebuffer( gl.FRAMEBUFFER, oldfbo );
}
};
/**
* Applies a depth-aware averaging filter.
*
* @param stateManager - x3dom webgl stateManager
* @param gl - WebGL context
* @param scene - Scene Node
* @param ssaoTexture - texture that contains the SSAO factor
* @param depthTexture - depth texture
* @param canvas - Canvas the scene is rendered on (needed for dimensions)
* @param fbo - FrameBufferObject handle that should be used as a target (null to use curent fbo)
*/
x3dom.SSAO.blur = function ( stateManager, gl, scene, ssaoTexture, depthTexture, canvas, fbo )
{
// save previous fbo
var oldfbo = gl.getParameter( gl.FRAMEBUFFER_BINDING );
if ( fbo != null )
{
gl.bindFramebuffer( gl.FRAMEBUFFER, fbo );
}
stateManager.frontFace( gl.CCW );
stateManager.disable( gl.CULL_FACE );
stateManager.disable( gl.DEPTH_TEST );
var sp = x3dom.SSAO.blurShaderProgram;
stateManager.useProgram( sp );
sp.SSAOTexture = 0;
sp.depthTexture = 1;
sp.depthThreshold = scene.getEnvironment()._vf.SSAOblurDepthTreshold;
var viewpoint = scene.getViewpoint();
var nearPlane = viewpoint.getNear();
var farPlane = viewpoint.getFar();
sp.nearPlane = nearPlane;
sp.farPlane = farPlane;
sp.depthReconstructionConstantA = ( farPlane + nearPlane ) / ( nearPlane - farPlane );
sp.depthReconstructionConstantB = ( 2.0 * farPlane * nearPlane ) / ( nearPlane - farPlane );
sp.pixelSize = [ 1.0 / canvas.width, 1.0 / canvas.height ];
sp.amount = scene.getEnvironment()._vf.SSAOamount;
// ssao texture
gl.activeTexture( gl.TEXTURE0 );
gl.bindTexture( gl.TEXTURE_2D, ssaoTexture );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE );
// depth texture
gl.activeTexture( gl.TEXTURE1 );
gl.bindTexture( gl.TEXTURE_2D, depthTexture );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE );
gl.texParameteri( gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE );
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, scene._fgnd._webgl.buffers[ x3dom.BUFFER_IDX.INDEX ] );
gl.bindBuffer( gl.ARRAY_BUFFER, scene._fgnd._webgl.buffers[ x3dom.BUFFER_IDX.POSITION ] );
gl.vertexAttribPointer( sp.position, 3, gl.FLOAT, false, 0, 0 );
gl.enableVertexAttribArray( sp.position );
gl.drawElements( scene._fgnd._webgl.primType, scene._fgnd._webgl.indexes.length, gl.UNSIGNED_SHORT, 0 );
gl.disableVertexAttribArray( sp.position );
gl.activeTexture( gl.TEXTURE0 );
gl.bindTexture( gl.TEXTURE_2D, null );
gl.activeTexture( gl.TEXTURE1 );
gl.bindTexture( gl.TEXTURE_2D, null );
// restore previous fbo
if ( fbo != null )
{
gl.bindFramebuffer( gl.FRAMEBUFFER, oldfbo );
}
};
/**
* Renders Screen-Space Ambeint Occlusion multiplicatively on top of the scene.
*
* @param stateManager - state manager for the WebGL context
* @param gl - WebGL context
* @param scene - current scene
* @param canvas - canvas that the scene is rendered to
*/
x3dom.SSAO.renderSSAO = function ( stateManager, gl, scene, canvas )
{
// set up resources if they are non-existent or if they are outdated:
this.reinitializeShadersIfNecessary( gl );
this.reinitializeRandomTextureIfNecessary( gl, scene );
this.reinitializeFBOIfNecessary( gl, canvas );
stateManager.viewport( 0, 0, canvas.width, canvas.height );
// render SSAO into fbo
this.render( stateManager, gl, scene, scene._webgl.fboScene.tex, canvas, x3dom.SSAO.fbo );
// render blurred SSAO multiplicatively
gl.enable( gl.BLEND );
gl.blendFunc( gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA );
this.blur( stateManager, gl, scene, x3dom.SSAO.fbotex, scene._webgl.fboScene.tex, canvas, null );
gl.disable( gl.BLEND );
};
/**
* X3DOM JavaScript Library
* http://www.x3dom.org
*
* (C)2009 Fraunhofer IGD, Darmstadt, Germany
* Dual licensed under the MIT and GPL
*
* Based on code originally provided by
* Philip Taylor: http://philip.html5.org
*/
/**
* Namespace container for Runtime module
* @namespace x3dom.runtime
*/
x3dom.runtime = {};
/**
* Runtime
*
* Runtime proxy object to get and set runtime parameters. This object
* is attached to each X3D element and can be used in the following manner:
*
* > var e = document.getElementById('the_x3delement');
* > e.runtime.showAll();
* > e.runtime.resetView();
* > ...
*
* @param doc
* @param canvas
* @constructor
*/
x3dom.Runtime = function ( doc, canvas )
{
this.doc = doc;
this.canvas = canvas;
this.config = {};
this.isReady = false;
this.fps = 0;
this.VRMode = false;
this.states = { measurements: [], infos: [] };
};
/**
* Add Measurement
*
* @param title
* @param value
*/
x3dom.Runtime.prototype.addMeasurement = function ( title, value )
{
this.states.measurements[ title ] = value;
};
/**
* Remove Measurement
*
* @param title
*/
x3dom.Runtime.prototype.removeMeasurement = function ( title )
{
if ( this.states.measurements[ title ] )
{
delete this.states.measurements[ title ];
}
};
/**
* Add Info
*
* @param title
* @param value
*/
x3dom.Runtime.prototype.addInfo = function ( title, value )
{
this.states.infos[ title ] = value;
};
/**
* Remove Info
*
* @param title
*/
x3dom.Runtime.prototype.removeInfo = function ( title )
{
delete this.states.infos[ title ];
};
/**
* Initialize
*
* @param doc
* @param canvas
*/
x3dom.Runtime.prototype.initialize = function ( doc, canvas )
{
this.doc = doc;
this.canvas = canvas;
// place to hold configuration data, i.e. flash backend path, etc.
// format and structure needs to be decided.
this.config = {};
this.isReady = false;
this.fps = 0;
};
/**
* APIFunction: noBackendFound
*
* This method is called once the system initialized and is not ready to
* render the first time because there is no backend found. By default this
* method noop. You can however override it with your own implementation.
*
* > x3dom.runtime.noBackendFound = function() {
* > alert("Dingel Dingel Ding Dong...");
* > }
*
* It is important to create this override before the document onLoad event has
* fired. Therefore putting it directly under the inclusion of x3dom.js is the
* preferred way to ensure overloading of this function.
*/
x3dom.Runtime.prototype.noBackendFound = function ()
{
x3dom.debug.logInfo( "No backend found. Unable to render." );
};
/**
* APIFunction: ready
*
* This method is called once the system initialized and is ready to render
* the first time. It is therefore possible to execute custom
* action by overriding this method in your code:
*
* > x3dom.runtime.ready = function() {
* > alert("About to render something the first time");
* > }
*
* It is important to create this override before the document onLoad event has fired.
* Therefore putting it directly under the inclusion of x3dom.js is the preferred
* way to ensure overloading of this function.
*
* Parameters:
* element - The x3d element this handler is acting upon
*/
x3dom.Runtime.prototype.ready = function ()
{
x3dom.debug.logInfo( "System ready." );
};
/**
* APIFunction: enterFrame
*
* This method is called just before the next frame is
* rendered. It is therefore possible to execute custom
* action by overriding this method in your code:
*
* > var element = document.getElementById('my_element');
* > element.runtime.enterFrame = function() {
* alert('hello custom enter frame');
* };
*
* If you have more than one X3D element in your HTML
* During initialization, just after ready() executed and before the very first frame
* is rendered, only the global override of this method works. If you need to execute
* code before the first frame renders, it is therefore best to use the ready()
* function instead.
*
* Parameters:
* element - The x3d element this handler is acting upon
*/
x3dom.Runtime.prototype.enterFrame = function ()
{
//x3dom.debug.logInfo('Render frame imminent');
// to be overwritten by user
};
/**
* APIFunction: exitFrame
*
* This method is called just after the current frame was
* rendered. It is therefore possible to execute custom
* action by overriding this method in your code:
*
* > var element = document.getElementById('my_element');
* > element.runtime.exitFrame = function() {
* alert('hello custom exit frame');
* };
*
* Parameters:
* element - The x3d element this handler is acting upon
*/
x3dom.Runtime.prototype.exitFrame = function ()
{
//x3dom.debug.logInfo('Render frame finished');
// to be overwritten by user
};
/**
* APIFunction: triggerRedraw
*
* Triggers a redraw of the scene.
*/
x3dom.Runtime.prototype.triggerRedraw = function ()
{
this.canvas.doc.needRender = true;
};
/**
* APIFunction: getActiveBindable
*
* Returns the currently active bindable DOM element of the given type.
* typeName must be a valid Bindable node (e.g. Viewpoint, Background, etc.).
*
* For example:
*
* > var element, bindable;
* > element = document.getElementById('the_x3delement');
* > bindable = element.runtime.getActiveBindable('background');
* > bindable.setAttribute('bind', 'false');
*
* @param typeName - Bindable type name
*
* @returns The active DOM element
*/
x3dom.Runtime.prototype.getActiveBindable = function ( typeName )
{
var stacks,
i,
current,
result,
type;
stacks = this.canvas.doc._bindableBag._stacks;
result = [];
type = x3dom.nodeTypesLC[ typeName.toLowerCase() ];
if ( !type )
{
x3dom.debug.logError( "No node of type \"" + typeName + "\" found." );
return null;
}
for ( i = 0; i < stacks.length; i++ )
{
current = stacks[ i ].getActive();
if ( current._xmlNode !== undefined && x3dom.isa( current, type ) )
{
result.push( current );
}
}
return result[ 0 ] ? result[ 0 ]._xmlNode : null;
};
/**
* APIFunction: nextView
*
* Navigates to the next viewpoint.
*/
x3dom.Runtime.prototype.nextView = function ()
{
var stack = this.canvas.doc._scene.getViewpoint()._stack;
if ( stack )
{
stack.switchTo( "next" );
}
else
{
x3dom.debug.logError( "No valid ViewBindable stack." );
}
};
/**
* APIFunction: prevView
*
* Navigates tho the previous viewpoint.
*/
x3dom.Runtime.prototype.prevView = function ()
{
var stack = this.canvas.doc._scene.getViewpoint()._stack;
if ( stack )
{
stack.switchTo( "prev" );
}
else
{
x3dom.debug.logError( "No valid ViewBindable stack." );
}
};
/**
* Function: viewpoint
*
* Returns the current viewpoint.
*
* @returns The viewpoint
*/
x3dom.Runtime.prototype.viewpoint = function ()
{
return this.canvas.doc._scene.getViewpoint();
};
/**
* Function: viewMatrix
*
* Returns the current view matrix.
*
* @returns Matrix object
*/
x3dom.Runtime.prototype.viewMatrix = function ()
{
return this.canvas.doc._viewarea.getViewMatrix();
};
/**
* Function: projectionMatrix
*
* Returns the current projection matrix.
*
* Returns:
* Matrix object
*/
x3dom.Runtime.prototype.projectionMatrix = function ()
{
return this.canvas.doc._viewarea.getProjectionMatrix();
};
/**
* Function: getWorldToCameraCoordinatesMatrix
*
* Returns the current world to camera coordinates matrix.
*
* @returns Matrix object
*/
x3dom.Runtime.prototype.getWorldToCameraCoordinatesMatrix = function ()
{
return this.canvas.doc._viewarea.getWCtoCCMatrix();
};
/**
* Function: getCameraToWorldCoordinatesMatrix
*
* Returns the current camera to world coordinates matrix.
*
* @returns Matrix object
*/
x3dom.Runtime.prototype.getCameraToWorldCoordinatesMatrix = function ()
{
return this.canvas.doc._viewarea.getCCtoWCMatrix();
};
/**
* Function: getViewingRay
*
* Returns the viewing ray for a given (x, y) position.
*
* @returns Ray object
*/
x3dom.Runtime.prototype.getViewingRay = function ( x, y )
{
return this.canvas.doc._viewarea.calcViewRay( x, y );
};
/**
* Function: shootRay
*
* Returns pickPosition, pickNormal, and pickObject for a given (x, y) position.
*
* @param x
* @param y
*
* @returns {{pickPosition: *, pickNormal: *, pickObject: null}}
*/
x3dom.Runtime.prototype.shootRay = function ( x, y )
{
var doc = this.canvas.doc;
var info = doc._viewarea._pickingInfo;
doc.onPick( this.canvas.gl, x, y );
return {
pickPosition : info.pickObj ? info.pickPos : null,
pickNormal : info.pickObj ? info.pickNorm : null,
pickObject : info.pickObj ? info.pickObj._xmlNode : null
};
};
/**
* Function: getWidth
*
* @returns the width of the canvas element.
*/
x3dom.Runtime.prototype.getWidth = function ()
{
return this.canvas.doc._viewarea._width;
};
/**
* Function: getHeight
*
* Returns the width of the canvas element.
*/
x3dom.Runtime.prototype.getHeight = function ()
{
return this.canvas.doc._viewarea._height;
};
/**
* Function: mousePosition
*
* Returns the 2d canvas layer position [x, y] for a given mouse event, i.e.,
* the mouse cursor's x and y positions relative to the canvas (x3d) element.
*/
x3dom.Runtime.prototype.mousePosition = function ( event )
{
var pos = this.canvas.mousePosition( event );
return [ pos.x, pos.y ];
};
/**
* Function: calcCanvasPos, calcPagePos
*
* Returns the 2d screen position [cx, cy] for a given point [wx, wy, wz] in world coordinates.
*
* @param wx
* @param wy
* @param wz
*/
x3dom.Runtime.prototype.calcCanvasPos = function ( wx, wy, wz )
{
var DPR = window.devicePixelRatio || 1;
var pnt = new x3dom.fields.SFVec3f( wx, wy, wz );
var mat = this.canvas.doc._viewarea.getWCtoCCMatrix();
var pos = mat.multFullMatrixPnt( pnt );
var w = this.canvas.doc._viewarea._width / DPR;
var h = this.canvas.doc._viewarea._height / DPR;
var x = Math.round( ( pos.x + 1 ) * ( w - 1 ) / 2 );
var y = Math.round( ( h - 1 ) * ( 1 - pos.y ) / 2 );
return [ x, y ];
};
x3dom.Runtime.prototype.calcPagePos = x3dom.Runtime.prototype.calcCanvasPos;
/**
* Function: getBBoxPoints
*
* @returns The eight point of the scene bounding box
*/
x3dom.Runtime.prototype.getBBoxPoints = function ()
{
var scene = this.canvas.doc._scene;
scene.updateVolume();
return [
{ x: scene._lastMin.x, y: scene._lastMin.y, z: scene._lastMin.z },
{ x: scene._lastMax.x, y: scene._lastMin.y, z: scene._lastMin.z },
{ x: scene._lastMin.x, y: scene._lastMax.y, z: scene._lastMin.z },
{ x: scene._lastMax.x, y: scene._lastMax.y, z: scene._lastMin.z },
{ x: scene._lastMin.x, y: scene._lastMin.y, z: scene._lastMax.z },
{ x: scene._lastMax.x, y: scene._lastMin.y, z: scene._lastMax.z },
{ x: scene._lastMin.x, y: scene._lastMax.y, z: scene._lastMax.z },
{ x: scene._lastMax.x, y: scene._lastMax.y, z: scene._lastMax.z }
];
};
/**
* Function: getSceneBRect
*
* @returns The 2d rect of the scene volume
*/
x3dom.Runtime.prototype.getSceneBRect = function ()
{
var min = { x: Number.MAX_VALUE, y: Number.MAX_VALUE };
var max = { x: Number.MIN_VALUE, y: Number.MIN_VALUE };
var points = this.getBBoxPoints();
for ( var i = 0; i < points.length; i++ )
{
var pos2D = this.calcCanvasPos( points[ i ].x, points[ i ].y, points[ i ].z );
min.x = ( pos2D[ 0 ] < min.x ) ? pos2D[ 0 ] : min.x;
min.y = ( pos2D[ 1 ] < min.y ) ? pos2D[ 1 ] : min.y;
max.x = ( pos2D[ 0 ] > max.x ) ? pos2D[ 0 ] : max.x;
max.y = ( pos2D[ 1 ] > max.y ) ? pos2D[ 1 ] : max.y;
}
var rect = {
x : min.x,
y : min.y,
width : max.x - min.x,
height : max.y - min.y
};
return rect;
};
/**
* Function: calcClientPos
*
* Returns the 2d client (returns the mouse coordinates relative to the window) position [cx, cy]
* for a given point [wx, wy, wz] in world coordinates.
*
* @param wx
* @param wy
* @param wz
* @returns {*}
*/
x3dom.Runtime.prototype.calcClientPos = function ( wx, wy, wz )
{
var elem = this.canvas.canvas.offsetParent;
if ( !elem )
{
x3dom.debug.logError( "Can't calc client pos without offsetParent." );
return [ 0, 0 ];
}
var canvasPos = elem.getBoundingClientRect();
var mousePos = this.calcCanvasPos( wx, wy, wz );
var compStyle = document.defaultView.getComputedStyle( elem, null );
var paddingLeft = parseFloat( compStyle.getPropertyValue( "padding-left" ) );
var borderLeftWidth = parseFloat( compStyle.getPropertyValue( "border-left-width" ) );
var paddingTop = parseFloat( compStyle.getPropertyValue( "padding-top" ) );
var borderTopWidth = parseFloat( compStyle.getPropertyValue( "border-top-width" ) );
var x = canvasPos.left + paddingLeft + borderLeftWidth + mousePos[ 0 ];
var y = canvasPos.top + paddingTop + borderTopWidth + mousePos[ 1 ];
return [ x, y ];
};
/**
* Function: getScreenshot
*
* Returns a Base64 encoded png image consisting of the current rendering.
*
* @eturns The Base64 encoded PNG image string
*/
x3dom.Runtime.prototype.getScreenshot = function ()
{
var url = "";
var backend = this.canvas.backend;
var canvas = this.canvas.canvas;
if ( canvas )
{
if ( backend == "flash" )
{
url = canvas.getScreenshot();
}
else
{
// first flip along y axis
var canvas2d = document.createElement( "canvas" );
canvas2d.width = canvas.width;
canvas2d.height = canvas.height;
var ctx = canvas2d.getContext( "2d" );
ctx.drawImage( canvas, 0, 0, canvas.width, canvas.height );
ctx.scale( 1, -1 );
ctx.translate( 0, -canvas.height );
url = canvas2d.toDataURL();
}
}
return url;
};
/**
* Function: getCanvas
*
* Returns the internal canvas element (only valid for WebGL backend)
*
* @returns The internal canvas element
*/
x3dom.Runtime.prototype.getCanvas = function ()
{
return this.canvas.canvas;
};
/**
* Function: lightMatrix
*
* Returns the current light matrix.
*
* @returns The light matrix
*/
x3dom.Runtime.prototype.lightMatrix = function ()
{
this.canvas.doc._viewarea.getLightMatrix();
};
/**
* APIFunction: resetView
*
* Resets the view to initial.
*/
x3dom.Runtime.prototype.resetView = function ()
{
this.canvas.doc._viewarea.resetView();
};
/**
* Function: lightView
*
* Navigates to the first light, if any.
*
* @returns True if navigation was possible, false otherwise.
*/
x3dom.Runtime.prototype.lightView = function ()
{
if ( this.canvas.doc._nodeBag.lights.length > 0 )
{
this.canvas.doc._viewarea.animateTo( this.canvas.doc._viewarea.getLightMatrix()[ 0 ],
this.canvas.doc._scene.getViewpoint() );
return true;
}
else
{
x3dom.debug.logInfo( "No lights to navigate to." );
return false;
}
};
/**
* APIFunction: uprightView
*
* Navigates to upright view
*/
x3dom.Runtime.prototype.uprightView = function ()
{
this.canvas.doc._viewarea.uprightView();
};
/**
* APIFunction: fitAll
*
* Zooms so that all objects are fully visible. Without change the actual Viewpoint orientation
*
* @param updateCenterOfRotation - a boolean value that specifies if the new center of rotation is set.
*/
x3dom.Runtime.prototype.fitAll = function ( updateCenterOfRotation )
{
if ( updateCenterOfRotation === undefined )
{
updateCenterOfRotation = true;
}
var scene = this.canvas.doc._scene;
scene.updateVolume();
this.canvas.doc._viewarea.fit( scene._lastMin, scene._lastMax, updateCenterOfRotation );
};
/**
* APIFunction: fitObject
*
* Zooms so that a given object are fully visible. Without change the actual Viewpoint orientation
*
* @param obj
* @param updateCenterOfRotation - a boolean value that specifies if the new center of rotation is set
*/
x3dom.Runtime.prototype.fitObject = function ( obj, updateCenterOfRotation )
{
if ( obj && obj._x3domNode )
{
if ( updateCenterOfRotation === undefined )
{
updateCenterOfRotation = true;
}
var min = x3dom.fields.SFVec3f.MAX();
var max = x3dom.fields.SFVec3f.MIN();
var vol = obj._x3domNode.getVolume();
vol.getBounds( min, max );
var mat = obj._x3domNode.getCurrentTransform();
min = mat.multMatrixPnt( min );
max = mat.multMatrixPnt( max );
// TODO: revise separation of "getVolume" and "getCurrentTransform"
// for the transform nodes - currently, both "overlap" because
// both include the transform's own matrix
// but which is what you usually expect from both methods...
if ( x3dom.isa( obj._x3domNode, x3dom.nodeTypes.X3DTransformNode ) )
{
var invMat = obj._x3domNode._trafo.inverse();
min = invMat.multMatrixPnt( min );
max = invMat.multMatrixPnt( max );
}
this.canvas.doc._viewarea.fit( min, max, updateCenterOfRotation );
}
};
/**
* APIFunction: showAll
*
* Zooms so that all objects are fully visible.
*
* @param axis - the axis as string: posX, negX, posY, negY, posZ, negZ
* @param updateCenterOfRotation - sets the center of rotation to the center of the scene volume
*/
x3dom.Runtime.prototype.showAll = function ( axis, updateCenterOfRotation )
{
this.canvas.doc._viewarea.showAll( axis, updateCenterOfRotation );
};
/**
* APIFunction: showObject
*
* Zooms so that a given object is fully visible in the middle of the screen.
*
* @param obj - the scene-graph element on which to focus
* @param axis - the axis as string: posX, negX, posY, negY, posZ, negZ
*/
x3dom.Runtime.prototype.showObject = function ( obj, axis )
{
if ( obj && obj._x3domNode )
{
if ( axis === undefined ) {axis = "negZ";}
var min = x3dom.fields.SFVec3f.MAX();
var max = x3dom.fields.SFVec3f.MIN();
var vol = obj._x3domNode.getVolume();
vol.getBounds( min, max );
var mat = obj._x3domNode.getCurrentTransform();
min = mat.multMatrixPnt( min );
max = mat.multMatrixPnt( max );
var viewarea = this.canvas.doc._viewarea;
// assume FOV_smaller as camera's fovMode
var focalLen = ( viewarea._width < viewarea._height ) ?
viewarea._width : viewarea._height;
var n0; // facingDir
switch ( axis )
{
case "posX": n0 = new x3dom.fields.SFVec3f( 1, 0, 0 ); break;
case "negX": n0 = new x3dom.fields.SFVec3f( -1, 0, 0 ); break;
case "posY": n0 = new x3dom.fields.SFVec3f( 0, 1, 0 ); break;
case "negY": n0 = new x3dom.fields.SFVec3f( 0, -1, 0 ); break;
case "posZ": n0 = new x3dom.fields.SFVec3f( 0, 0, 1 ); break;
case "negZ": n0 = new x3dom.fields.SFVec3f( 0, 0, -1 ); break;
}
var viewpoint = this.canvas.doc._scene.getViewpoint();
var fov = viewpoint.getFieldOfView() / 2.0;
var ta = Math.tan( fov );
if ( Math.abs( ta ) > x3dom.fields.Eps )
{
focalLen /= ta;
}
var w = viewarea._width - 1;
var h = viewarea._height - 1;
var frame = 0.25;
var minScreenPos = new x3dom.fields.SFVec2f( frame * w, frame * h );
frame = 0.75;
var maxScreenPos = new x3dom.fields.SFVec2f( frame * w, frame * h );
var dia2 = max.subtract( min ).multiply( 0.5 ); // half diameter
var rw = dia2.length(); // approx radius
var pc = min.add( dia2 ); // center in wc
var vc = maxScreenPos.subtract( minScreenPos ).multiply( 0.5 );
var rs = 1.5 * vc.length();
vc = vc.add( minScreenPos );
var dist = 1.0;
if ( rs > x3dom.fields.Eps )
{
dist = ( rw / rs ) * Math.sqrt( vc.x * vc.x + vc.y * vc.y + focalLen * focalLen );
}
n0 = mat.multMatrixVec( n0 ).normalize();
n0 = n0.multiply( dist );
var p0 = pc.add( n0 );
var qDir = x3dom.fields.Quaternion.rotateFromTo( new x3dom.fields.SFVec3f( 0, 0, 1 ), n0 );
var R = qDir.toMatrix();
var T = x3dom.fields.SFMatrix4f.translation( p0.negate() );
var M = x3dom.fields.SFMatrix4f.translation( p0 );
M = M.mult( R ).mult( T ).mult( M );
var viewmat = M.inverse();
viewarea.animateTo( viewmat, viewpoint );
}
};
/**
* APIMethod animateViewpointTo
*
* Animates the current viewpoint to a new location.
*
* @param target - The taget view matrix or a viewpoint node
* @param duration - The animation duration
*/
x3dom.Runtime.prototype.animateViewpointTo = function ( target, duration )
{
var viewarea = this.canvas.doc._viewarea;
var viewpoint = this.canvas.doc._scene.getViewpoint();
if ( target._x3domNode != undefined )
{
target = target._x3domNode;
}
viewarea.animateTo( target, viewpoint, duration );
};
/**
* APIMethod getCenter
*
* Returns the center of a X3DShapeNode or X3DGeometryNode.
*
* @param domNode - the node for which its center shall be returned
*
* @returns Node center (or null if no Shape or Geometry)
*/
x3dom.Runtime.prototype.getCenter = function ( domNode )
{
if ( domNode && domNode._x3domNode &&
( this.isA( domNode, "X3DShapeNode" ) || this.isA( domNode, "X3DGeometryNode" ) ) )
{
return domNode._x3domNode.getCenter();
}
return null;
};
/**
* APIMethod getCurrentTransform
*
* Returns the current to world transformation of a node.
*
* @param domNode - the node for which its transformation shall be returned
*
* @returns Transformation matrix (or null no valid node is given)
*/
x3dom.Runtime.prototype.getCurrentTransform = function ( domNode )
{
if ( domNode && domNode._x3domNode )
{
return domNode._x3domNode.getCurrentTransform();
}
return null;
};
/**
* APIMethod getBBox
*
* Returns the bounding box of a node.
*
* @param domNode - the node for which its volume shall be returned
*
* @returns The min and max positions of the node's bounding box.
*/
x3dom.Runtime.prototype.getBBox = function ( domNode )
{
if ( domNode && domNode._x3domNode && this.isA( domNode, "X3DBoundedObject" ) )
{
var vol = domNode._x3domNode.getVolume();
return {
min : x3dom.fields.SFVec3f.copy( vol.min ),
max : x3dom.fields.SFVec3f.copy( vol.max )
};
}
return null;
};
/**
* APIMethod getSceneBBox
*
* Returns the bounding box of the scene.
*
* @returns The min and max positions of the scene's bounding box.
*/
x3dom.Runtime.prototype.getSceneBBox = function ()
{
var scene = this.canvas.doc._scene;
scene.updateVolume();
return {
min : x3dom.fields.SFVec3f.copy( scene._lastMin ),
max : x3dom.fields.SFVec3f.copy( scene._lastMax )
};
};
/**
* APIFunction: debug
*
* Displays or hides the debug window. If parameter is omitted,
* the current visibility status is returned.
*
* @param show - true to show debug window, false to hide
*
* @returns Current visibility status of debug window (true=visible, false=hidden)
*/
x3dom.Runtime.prototype.debug = function ( show )
{
var doc = this.canvas.doc;
if ( doc._viewarea._visDbgBuf === undefined )
{
doc._viewarea._visDbgBuf = ( doc._x3dElem.getAttribute( "showLog" ) === "true" );
}
if ( arguments.length > 0 )
{
if ( show === true )
{
doc._viewarea._visDbgBuf = true;
x3dom.debug.logContainer.style.display = "block";
}
else
{
doc._viewarea._visDbgBuf = false;
x3dom.debug.logContainer.style.display = "none";
}
}
else
{
doc._viewarea._visDbgBuf = !doc._viewarea._visDbgBuf;
x3dom.debug.logContainer.style.display = ( doc._viewarea._visDbgBuf == true ) ? "block" : "none";
}
doc.needRender = true;
return doc._viewarea._visDbgBuf;
};
/**
* APIFunction: navigationType
*
* Readout of the currently active navigation.
*
* @returns A string representing the active navigation type
*/
x3dom.Runtime.prototype.navigationType = function ()
{
return this.canvas.doc._scene.getNavigationInfo().getType();
};
/**
* APIFunction: noNav
*
* Switches to noNav mode
*/
x3dom.Runtime.prototype.noNav = function ()
{
this.canvas.doc._scene.getNavigationInfo().setType( "none" );
};
/**
* APIFunction: examine
*
* Switches to examine mode
*/
x3dom.Runtime.prototype.examine = function ()
{
this.canvas.doc._scene.getNavigationInfo().setType( "examine" );
};
/**
* APIFunction: turnTable
*
* Switches to turnTable mode
*/
x3dom.Runtime.prototype.turnTable = function ()
{
this.canvas.doc._scene.getNavigationInfo().setType( "turntable" );
};
/**
* APIFunction: fly
*
* Switches to fly mode
*/
x3dom.Runtime.prototype.fly = function ()
{
this.canvas.doc._scene.getNavigationInfo().setType( "fly" );
};
/**
* APIFunction: freeFly
*
* Switches to freeFly mode
*/
x3dom.Runtime.prototype.freeFly = function ()
{
this.canvas.doc._scene.getNavigationInfo().setType( "freefly" );
};
/**
* APIFunction: lookAt
*
* Switches to lookAt mode
*/
x3dom.Runtime.prototype.lookAt = function ()
{
this.canvas.doc._scene.getNavigationInfo().setType( "lookat" );
};
/**
* APIFunction: lookAround
*
* Switches to lookAround mode
*/
x3dom.Runtime.prototype.lookAround = function ()
{
this.canvas.doc._scene.getNavigationInfo().setType( "lookaround" );
};
/**
* APIFunction: walk
*
* Switches to walk mode
*/
x3dom.Runtime.prototype.walk = function ()
{
this.canvas.doc._scene.getNavigationInfo().setType( "walk" );
};
/**
* APIFunction: game
*
* Switches to game mode
*/
x3dom.Runtime.prototype.game = function ()
{
this.canvas.doc._scene.getNavigationInfo().setType( "game" );
};
/**
* APIFunction: helicopter
*
* Switches to helicopter mode
*/
x3dom.Runtime.prototype.helicopter = function ()
{
this.canvas.doc._scene.getNavigationInfo().setType( "helicopter" );
};
/**
* Function: resetExamin
*
* Resets all variables required by examine mode to init state
*/
x3dom.Runtime.prototype.resetExamin = function ()
{
var viewarea = this.canvas.doc._viewarea;
viewarea._rotMat = x3dom.fields.SFMatrix4f.identity();
viewarea._transMat = x3dom.fields.SFMatrix4f.identity();
viewarea._movement = new x3dom.fields.SFVec3f( 0, 0, 0 );
viewarea._needNavigationMatrixUpdate = true;
this.canvas.doc.needRender = true;
};
/**
* APIFunction: disableKeys
*
* Disable keys
*/
x3dom.Runtime.prototype.disableKeys = function ()
{
this.canvas.disableKeys = true;
};
/**
* APIFunction: enableKeys
*
* Enable keys
*/
x3dom.Runtime.prototype.enableKeys = function ()
{
this.canvas.disableKeys = false;
};
/**
* APIFunction: disableLeftDrag
*
* Disable left drag
*/
x3dom.Runtime.prototype.disableLeftDrag = function ()
{
this.canvas.disableLeftDrag = true;
};
/**
* APIFunction: enableLeftDrag
*
* Enable left drag
*/
x3dom.Runtime.prototype.enableLeftDrag = function ()
{
this.canvas.disableLeftDrag = false;
};
/**
* APIFunction: disableRightDrag
*
* Disable right drag
*/
x3dom.Runtime.prototype.disableRightDrag = function ()
{
this.canvas.disableRightDrag = true;
};
/**
* APIFunction: enableRightDrag
*
* Enable right drag
*/
x3dom.Runtime.prototype.enableRightDrag = function ()
{
this.canvas.disableRightDrag = false;
};
/**
* APIFunction: disableMiddleDrag
*
* Disable middle drag
*/
x3dom.Runtime.prototype.disableMiddleDrag = function ()
{
this.canvas.disableMiddleDrag = true;
};
/**
* APIFunction: enableMiddleDrag
*
* Enable right drag
*/
x3dom.Runtime.prototype.enableMiddleDrag = function ()
{
this.canvas.disableMiddleDrag = false;
};
/**
* Function: togglePoints
*
* Toggles points attribute
*
* @param lines
* @returns {*}
*/
x3dom.Runtime.prototype.togglePoints = function ( lines )
{
var doc = this.canvas.doc;
var mod = ( lines === true ) ? 3 : 2;
doc._viewarea._points = ++doc._viewarea._points % mod;
doc.needRender = true;
return doc._viewarea._points;
};
/**
* Function: pickRect
*
* Returns an array of all shape elements that are within the picked rectangle
* defined by (x1, y1) and (x2, y2) in canvas coordinates
*
* @param x1
* @param y1
* @param x2
* @param y2
* @returns {*}
*/
x3dom.Runtime.prototype.pickRect = function ( x1, y1, x2, y2 )
{
return this.canvas.doc.onPickRect( this.canvas.gl, x1, y1, x2, y2 );
};
/**
* Function: pickMode
*
* Get the current pickMode intersect type
*
* @param internal - true/false. If given return the internal representation.
* Only use for debugging.
*
* @returns The current intersect type value suitable to use with changePickMode
* If parameter is, given, provide with internal representation.
*/
x3dom.Runtime.prototype.pickMode = function ( options )
{
if ( options && options.internal === true )
{
return this.canvas.doc._scene._vf.pickMode;
}
return this.canvas.doc._scene._vf.pickMode.toLowerCase();
};
/**
* Function: changePickMode
*
* Alter the value of intersect type. Can be one of: box, idBuf, idBuf24, idBufId, color, texCoord.
* Other values are ignored.
*
* @param type - The new intersect type: box, idBuf, idBuf24, idBufId, color, texCoord
*
* @returns true if the type has been changed, false otherwise
*/
x3dom.Runtime.prototype.changePickMode = function ( type )
{
// pick type one of : box, idBuf, idBuf24, idBufId, color, texCoord
type = type.toLowerCase();
switch ( type )
{
case "idbuf": type = "idBuf"; break;
case "idbuf24": type = "idBuf24"; break;
case "idbufid": type = "idBufId"; break;
case "texcoord": type = "texCoord"; break;
case "color": type = "color"; break;
case "box": type = "box"; break;
default:
x3dom.debug.logWarning( "Switch pickMode to " + type + " unknown intersect type" );
type = undefined;
}
if ( type !== undefined )
{
this.canvas.doc._scene._vf.pickMode = type;
x3dom.debug.logInfo( "Switched pickMode to '" + type + "'." );
return true;
}
return false;
};
/**
* APIFunction: speed
*
* Get the current speed value. If parameter is given the new speed value is set.
*
* @param newSpeed - The new speed value (optional)
*
* @returns The current speed value
*/
x3dom.Runtime.prototype.speed = function ( newSpeed )
{
var navi = this.canvas.doc._scene.getNavigationInfo();
if ( newSpeed )
{
navi._vf.speed = newSpeed;
x3dom.debug.logInfo( "Changed navigation speed to " + navi._vf.speed );
}
return navi._vf.speed;
};
/**
* APIFunction: zoom
*
* Modifies the zoom of current viewpoint with the specified zoom value.
*
* @param zoomAmount - The zoom amount
*/
x3dom.Runtime.prototype.zoom = function ( zoomAmount )
{
this.canvas.doc._viewarea.zoom( zoomAmount );
this.canvas.doc.needRender = true;
};
/**
* APIFunction: statistics
*
* Get or set statistics info. If parameter is omitted, this method
* only returns the the visibility status of the statistics info overlay.
*
* @param mode - true or false. To enable or disable the statistics info
*
* @returns The current visibility of the statistics info (true = visible, false = invisible)
*/
x3dom.Runtime.prototype.statistics = function ( mode )
{
var states = this.canvas.stateViewer;
if ( states )
{
this.canvas.doc.needRender = true;
if ( mode === true )
{
states.display( mode );
return true;
}
else if ( mode === false )
{
states.display( mode );
return false;
}
else
{
states.display( !states.active );
// if no parameter is given return current status (false = not visible, true = visible)
return states.active;
}
}
return false;
};
/**
* Function: processIndicator
*
* Enable or disable the process indicator. If parameter is omitted, this method
* only returns the the visibility status of the progress bar overlay.
*
* @param mode - true or false. To enable or disable the progress indicator
*
* @returns The current visibility of the progress indicator info (true = visible, false = invisible)
*/
x3dom.Runtime.prototype.processIndicator = function ( mode )
{
var processDiv = this.canvas.progressDiv;
if ( processDiv )
{
if ( mode === true )
{
processDiv.style.display = "flex";
return true;
}
else if ( mode === false )
{
processDiv.style.display = "none";
return false;
}
// if no parameter is given return current status (false = not visible, true = visible)
return processDiv.style.display != "none";
}
return false;
};
/**
* Get properties
*
* @returns {*}
*/
x3dom.Runtime.prototype.properties = function ()
{
return this.canvas.doc.properties;
};
/**
* Get current backend name
*
* @returns {*}
*/
x3dom.Runtime.prototype.backendName = function ()
{
return this.canvas.backend;
};
/**
* Get current framerate
*
* @returns {number}
*/
x3dom.Runtime.prototype.getFPS = function ()
{
return this.fps;
};
/**
* APIMethod isA
*
* Test a DOM node object against a node type string. This method
* can be used to determine the "type" of a DOM node.
*
* @param domNode - the node to test for
* @param nodeType - node name to test domNode against
*
* @returns True or false
*/
x3dom.Runtime.prototype.isA = function ( domNode, nodeType )
{
var inherits = false;
if ( nodeType && domNode && domNode._x3domNode )
{
if ( nodeType === "" )
{
nodeType = "X3DNode";
}
inherits = x3dom.isa( domNode._x3domNode,
x3dom.nodeTypesLC[ nodeType.toLowerCase() ] );
}
return inherits;
};
/**
* APIMethod getPixelScale
*
* Returns the virtual scale of one pixel for the current orthographic viewpoint.
* The returned vector contains scale values for the x and y direction. The z value is always null.
*
* @returns x3dom.fields.SFVec3f or null if non orthographic view
*/
x3dom.Runtime.prototype.getPixelScale = function ()
{
var vp = this.viewpoint();
if ( !x3dom.isa( vp, x3dom.nodeTypes.OrthoViewpoint ) )
{
x3dom.debug.logError( "getPixelScale is only implemented for orthographic Viewpoints" );
return null;
}
var zoomLevel = vp.getZoom();
var left = zoomLevel[ 0 ];
var bottom = zoomLevel[ 1 ];
var right = zoomLevel[ 2 ];
var top = zoomLevel[ 3 ];
var x = right - left;
var y = top - bottom;
var pixelScaleX = x / this.getWidth();
var pixelScaleY = y / this.getHeight();
return new x3dom.fields.SFVec3f( pixelScaleX, pixelScaleY, 0.0 );
};
/**
* APIMethod onAnimationStarted
*
*/
x3dom.Runtime.prototype.onAnimationStarted = function ()
{
//x3dom.debug.logInfo('Render frame finished');
// to be overwritten by user
};
/**
* APIMethod onAnimationFinished
*
*/
x3dom.Runtime.prototype.onAnimationFinished = function ()
{
//x3dom.debug.logInfo('Render frame finished');
// to be overwritten by user
};
x3dom.Runtime.prototype.enterVR = function ()
{
this.canvas.enterVR();
};
x3dom.Runtime.prototype.exitVR = function ()
{
this.canvas.exitVR();
};
x3dom.Runtime.prototype.toggleVR = function ()
{
if ( !this.canvas.xrSession )
{
this.enterVR();
}
else if ( this.canvas.xrSession )
{
this.exitVR();
}
};
/**
* APIMethod toggleProjection
*
* @param perspViewID
* @param orthoViewID
* @returns {number}
*/
x3dom.Runtime.prototype.toggleProjection = function ( perspViewID, orthoViewID )
{
var dist;
var factor = 2.2;
var runtime = document.getElementById( "x3d" ).runtime;
var navInfo = runtime.canvas.doc._scene.getNavigationInfo();
var speed = navInfo._vf.transitionTime;
var persp = document.getElementById( perspViewID )._x3domNode;
var ortho = document.getElementById( orthoViewID )._x3domNode;
navInfo._vf.transitionTime = 0;
ortho._bindAnimation = false;
persp._bindAnimation = false;
if ( persp._vf.isActive )
{
ortho._viewMatrix = persp._viewMatrix;
document.getElementById( orthoViewID ).setAttribute( "set_bind", "true" );
dist = persp._viewMatrix.e3().length() / factor;
ortho.setZoom( dist );
}
else if ( ortho._vf.isActive )
{
persp._viewMatrix = ortho._viewMatrix;
document.getElementById( perspViewID ).setAttribute( "set_bind", "true" );
dist = ortho._fieldOfView[ 2 ] * -factor;
var translation = ortho._viewMatrix.e2().normalize().multiply( dist );
persp._viewMatrix.setTranslate( translation );
}
navInfo._vf.transitionTime = speed;
ortho._bindAnimation = true;
persp._bindAnimation = true;
return ( persp._vf.isActive ) ? 0 : 1;
};
/**
* APIFunction: replaceWorld
*
* Replaces the current scene element
*
* For example:
* > var element, x3d, jsobject, optionalUrl;
* > element = document.getElementById('the_x3delement');
* > x3d = element.runtime.createX3DFromJS(jsobject, optionalUrl);
* > element.runtime.replaceWorld(x3d);
*
* @param scene - scene element to substitute
*
* @note replaceWorld replaces the current x3d element. It is
* therefore necessary to get the replaced x3d element
* each time to access the new runtime.
*/
x3dom.Runtime.prototype.replaceWorld = function ( scene )
{
var x3dElement,
child,
name;
if ( scene.localName.toUpperCase() === "X3D" )
{
x3dElement = scene.cloneNode( false );
}
else
{
x3dElement = this.doc.cloneNode( false );
}
//clean x3d element
while ( child = scene.firstChild )
{
name = child.nodeType === 1 ? child.localName.toUpperCase() : null;
if ( name == "HEAD" || name == "SCENE" )
{
x3dElement.appendChild( child );
}
else
{
child.remove();
}
}
this.doc.parentNode.replaceChild( x3dElement, this.doc );
this.doc = x3dElement;
x3dom.reload();
return;
};
/**
* APIFunction: createX3DFromJS
*
* Creates a x3d element from a JSON JavaScript X3D object
*
* For example:
* > var element, x3d, jsobject, optionalUrl;
* > element = document.getElementById('the_x3delement');
* > x3d = element.runtime.createX3DFromJS(jsobject, optionalUrl);
* > element.runtime.replaceWorld(x3d);
*
* @param jsobject - JavaScript JSON object of X3D object
* @param optionalURL - if specified, does a PROTO expansion on jsobject.
* JSON ExternProtoDeclare's are loaded relative to this URL.
*
* @returns The x3d element
*/
x3dom.Runtime.prototype.createX3DFromJS = function ( jsobject, optionalURL )
{
if ( optionalURL )
{
jsobject = x3dom.protoExpander.prototypeExpander( optionalURL, jsobject );
}
var jsonParser = new x3dom.JSONParser();
return jsonParser.parseJavaScript( jsobject );
};
/**
* APIFunction: createX3DFromString
*
* Creates a x3d element from a JSON or XML String
*
* For example:
*
* > var element, x3d, jsonOrXML, optionalUrl;
* > element = document.getElementById('the_x3delement');
* > x3d = element.runtime.createX3DFromString(jsonOrXML, optionalUrl);
* > element.runtime.replaceWorld(x3d);
*
* @param jsonOrXML - JSON or XML of X3D object
* @param optionalURL - if specified, does a PROTO expansion on json.
* JSON ExternProtoDeclare's are loaded relative to this URL.
*
* @returns The x3d element
*/
x3dom.Runtime.prototype.createX3DFromString = function ( jsonOrXML, optionalURL )
{
try
{
var jsobject = JSON.parse( jsonOrXML );
return this.createX3DFromJS( jsobject, optionalURL );
}
catch ( e )
{
var parser = new DOMParser();
var doc = parser.parseFromString( jsonOrXML, "application/xml" );
var scene = doc.querySelector( "X3D" );
if ( scene == null )
{
doc = parser.parseFromString( jsonOrXML, "text/html" );
scene = doc.querySelector( "X3D" );
}
return scene;
}
};
/**
* APIFunction: createX3DFromURLPromise
*
* Creates a Promise resolved to the x3d element from a Url
*
* For example:
* > var element, x3d, json, optionalUrl;
* > element = document.getElementById('the_x3delement');
* > x3d = element.runtime.createX3DFromURK(Url, optionalUrl);
* > element.runtime.replaceWorld(x3d);
*
* @param url - url of XML or JSON of X3D object
* @param optionalURL - if specified, does a PROTO expansion on json, only.
* JSON ExternProtoDeclare's are loaded relative to this URL.
*
* @returns A Promise resolved to the x3d element
*/
x3dom.Runtime.prototype.createX3DFromURLPromise = function ( url, optionalURL )
{
this.canvas.doc.incrementDownloads();
var that = this;
return fetch( url )
.then( function ( r ) { return r.text(); } )
.then( function ( text )
{
that.canvas.doc.decrementDownloads();
return that.createX3DFromString( text, optionalURL );
} )
.catch( function ( r )
{
that.canvas.doc.decrementDownloads();
x3dom.debug.logError( "fetch failed: " + r );
return null;
} );
};
/**
* APIFunction: loadURL
*
* loads asynchronuously a scene from a URL with json or xml content.
* The function returns before the world is loaded. Use events or .ready
* to determine when the scene is available.
* For more control use .createX3DFromURLPromise(url, optionalURL).
*
* Example:
* > var element, url , optionalUrl;
* > element.runtime.loadURL(url, optionalUrl);
*
* @param url - url of XML or JSON of X3D object
* @param optionalURL - if specified, does a PROTO expansion on json, only.
* JSON ExternProtoDeclare's are loaded relative to this URL.
*
* @returns undefined
*
* @note replaceWorld replaces the current x3d element. It is
* therefore necessary to get the replaced x3d element
* each time to access the new runtime.
*/
x3dom.Runtime.prototype.loadURL = function ( url, optionalURL )
{
var that = this;
this.createX3DFromURLPromise( url, optionalURL )
.then( function ( x3d )
{
if ( x3d != null ) {that.replaceWorld( x3d );}
else {x3dom.debug.logError( "loadURL: could not fetch or parse " + url );}
} );
};
/**
* X3DOM JavaScript Library
* http://www.x3dom.org
*
* (C)2009 Fraunhofer IGD, Darmstadt, Germany
* Dual licensed under the MIT and GPL
*
* Based on code originally provided by
* Philip Taylor: http://philip.html5.org
*/
/**
* Holds the UserAgent feature
*/
x3dom.userAgentFeature = {
supportsDOMAttrModified : false,
supportsMutationObserver : true
};
( function loadX3DOM ()
{
"use strict";
var onload = function ()
{
var i,
j; // counters
// Search all X3D elements in the page
var x3ds_unfiltered = document.getElementsByTagName( "X3D" );
var x3ds = [];
// check if element already has been processed
for ( i = 0; i < x3ds_unfiltered.length; i++ )
{
if ( x3ds_unfiltered[ i ].hasRuntime === undefined )
{x3ds.push( x3ds_unfiltered[ i ] );}
}
// Components and params
var params;
var settings = new x3dom.Properties(); // stores the stuff in <param>
var validParams = array_to_object( [
"showLog",
"showStat",
"showProgress",
"PrimitiveQuality",
"components",
"loadpath",
"disableDoubleClick",
"backend",
"altImg",
"runtimeEnabled",
"disableKeys",
"showTouchpoints",
"disableTouch",
"maxActiveDownloads",
"useGeoCache",
"baseURL"
] );
var showLoggingConsole = false;
// for each X3D element
for ( i = 0; i < x3ds.length; i++ )
{
// default parameters
settings.setProperty( "showLog", x3ds[ i ].getAttribute( "showLog" ) || "false" );
settings.setProperty( "showStat", x3ds[ i ].getAttribute( "showStat" ) || "false" );
settings.setProperty( "showProgress", x3ds[ i ].getAttribute( "showProgress" ) || "true" );
settings.setProperty( "PrimitiveQuality", x3ds[ i ].getAttribute( "PrimitiveQuality" ) || "High" );
settings.setProperty( "useGeoCache", x3ds[ i ].getAttribute( "useGeoCache" ) || "true" );
settings.setProperty( "baseURL", x3ds[ i ].getAttribute( "baseURL" ) || "" );
// for each param element inside the X3D element
// add settings to properties object
params = x3ds[ i ].getElementsByTagName( "PARAM" );
for ( j = 0; j < params.length; j++ )
{
if ( params[ j ].getAttribute( "name" ) in validParams )
{
settings.setProperty( params[ j ].getAttribute( "name" ), params[ j ].getAttribute( "value" ) );
}
else
{
// x3dom.debug.logError("Unknown parameter: " + params[j].getAttribute('name'));
}
}
// enable log
if ( settings.getProperty( "showLog" ) === "true" )
{
showLoggingConsole = true;
}
}
if ( showLoggingConsole == true )
{
x3dom.debug.activate( true );
}
else
{
x3dom.debug.activate( false );
}
// Convert the collection into a simple array (is this necessary?)
x3ds = x3ds.map( function ( n )
{
n.hasRuntime = true;
return n;
} );
if ( x3dom.about !== undefined )
{
x3dom.debug.logInfo( "X3DOM " + x3dom.about.version + ", " +
"Build: " + x3dom.about.build + ", " +
"Revison: <a href='https://github.com/x3dom/x3dom/tree/" + x3dom.about.revision + "'>"
+ x3dom.about.revision + "</a>, " +
"Date: " + x3dom.about.date );
}
x3dom.debug.logInfo( "Found " + x3ds.length + " X3D and nodes..." );
// Create a HTML canvas for every X3D scene and wrap it with
// an X3D canvas and load the content
var x3d_element,
x3dcanvas,
altDiv,
altP,
aLnk,
altImg,
t0,
t1;
for ( i = 0; i < x3ds.length; i++ )
{
x3d_element = x3ds[ i ];
x3dcanvas = new x3dom.X3DCanvas( x3d_element, x3dom.canvases.length );
x3dom.canvases.push( x3dcanvas );
if ( x3dcanvas.gl === null )
{
altDiv = document.createElement( "div" );
altDiv.setAttribute( "class", "x3dom-nox3d" );
altDiv.setAttribute( "id", "x3dom-nox3d" );
altP = document.createElement( "p" );
altP.appendChild( document.createTextNode( "WebGL is not yet supported in your browser. " ) );
aLnk = document.createElement( "a" );
aLnk.setAttribute( "href", "http://www.x3dom.org/?page_id=9" );
aLnk.appendChild( document.createTextNode( "Follow link for a list of supported browsers... " ) );
altDiv.appendChild( altP );
altDiv.appendChild( aLnk );
x3dcanvas.x3dElem.appendChild( altDiv );
// remove the stats div (it's not added when WebGL doesn't work)
if ( x3dcanvas.stateViewer )
{
x3d_element.removeChild( x3dcanvas.stateViewer.viewer );
}
continue;
}
t0 = Date.now();
x3ds[ i ].runtime = new x3dom.Runtime( x3ds[ i ], x3dcanvas );
x3ds[ i ].runtime.initialize( x3ds[ i ], x3dcanvas );
if ( x3dom.runtime.ready )
{
x3ds[ i ].runtime.ready = x3dom.runtime.ready;
}
// no backend found method system wide call
if ( x3dcanvas.backend == "" )
{
x3dom.runtime.noBackendFound();
}
x3dcanvas.load( x3ds[ i ], i, settings );
// show or hide statistics based on param/x3d attribute settings
if ( settings.getProperty( "showStat" ) === "true" )
{
x3ds[ i ].runtime.statistics( true );
}
else
{
x3ds[ i ].runtime.statistics( false );
}
if ( settings.getProperty( "showProgress" ) === "true" )
{
if ( settings.getProperty( "showProgress" ) === "bar" )
{
x3dcanvas.progressDiv.setAttribute( "class", "x3dom-progress bar" );
}
x3ds[ i ].runtime.processIndicator( true );
}
else
{
x3ds[ i ].runtime.processIndicator( false );
}
t1 = Date.now() - t0;
x3dom.debug.logInfo( "Time for setup and init of GL element no. " + i + ": " + t1 + " ms." );
}
var ready = ( function ( eventType )
{
var evt = null;
if ( document.createEvent )
{
evt = document.createEvent( "Events" );
evt.initEvent( eventType, true, true );
document.dispatchEvent( evt );
}
else if ( document.createEventObject )
{
evt = document.createEventObject();
// http://stackoverflow.com/questions/1874866/how-to-fire-onload-event-on-document-in-ie
document.body.fireEvent( "on" + eventType, evt );
}
} )( "load" );
};
var onunload = function ()
{
if ( x3dom.canvases && x3dom.canvases.length > 0 && x3dom.canvases[ 0 ].doc )
{
for ( var i = 0; i < x3dom.canvases.length; i++ )
{
x3dom.canvases[ i ].doc.shutdown( x3dom.canvases[ i ].gl );
}
x3dom.canvases = [];
}
};
// Initializes an <x3d> root element that was added after document load.
x3dom.reload = function ()
{
onload();
};
if ( window.addEventListener )
{
window.addEventListener( "load", onload, false );
window.addEventListener( "unload", onunload, false );
window.addEventListener( "reload", onunload, false );
}
else if ( window.attachEvent )
{
window.attachEvent( "onload", onload );
window.attachEvent( "onunload", onunload );
window.attachEvent( "onreload", onunload );
}
// Initialize if we were loaded after 'DOMContentLoaded' already fired.
// This can happen if the script was loaded by other means.
if ( document.readyState === "complete" )
{
window.setTimeout( function () { onload(); }, 20 );
}
} )();
/**
* X3DOM JavaScript Library
* http://www.x3dom.org
*
* (C)2009 Fraunhofer IGD, Darmstadt, Germany
* Dual licensed under the MIT and GPL
*
* Based on code originally provided by
* Philip Taylor: http://philip.html5.org
*/
/**
* Cache Constructor
*
* @constructor
*/
x3dom.Cache = function ()
{
this.textures = [];
this.shaders = [];
};
/**
* Returns a Texture 2D
*
* @param gl
* @param doc
* @param url
* @param bgnd
* @param crossOrigin
* @param scale
* @param genMipMaps
*
* @returns {*}
*/
x3dom.Cache.prototype.getTexture2D = function ( gl, doc, url, bgnd, crossOrigin, scale, genMipMaps, flipY, tex )
{
var textureIdentifier = url;
if ( this.textures[ textureIdentifier ] === undefined )
{
this.textures[ textureIdentifier ] = x3dom.Utils.createTexture2D(
gl, doc, url, bgnd, crossOrigin, scale, genMipMaps, flipY, tex );
}
return this.textures[ textureIdentifier ];
};
/**
* Returns a Texture 2D
*
* @param gl
* @param nameSpace
* @param def
*
* @returns {*}
*/
x3dom.Cache.prototype.getTexture2DByDEF = function ( gl, nameSpace, def )
{
var textureIdentifier = nameSpace.name + "_" + def;
if ( this.textures[ textureIdentifier ] === undefined )
{
this.textures[ textureIdentifier ] = gl.createTexture();
}
return this.textures[ textureIdentifier ];
};
/**
* Returns a Cube Texture
*
* @param gl
* @param doc
* @param url
* @param bgnd
* @param crossOrigin
* @param scale
* @param genMipMaps
*
* @returns {*}
*/
x3dom.Cache.prototype.getTextureCube = function ( gl, doc, url, bgnd, crossOrigin, scale, genMipMaps, flipY )
{
var textureIdentifier = "";
for ( var i = 0; i < url.length; ++i )
{
textureIdentifier += url[ i ] + "|";
}
if ( this.textures[ textureIdentifier ] === undefined )
{
this.textures[ textureIdentifier ] = x3dom.Utils.createTextureCube(
gl, doc, url, bgnd, crossOrigin, scale, genMipMaps, flipY );
}
return this.textures[ textureIdentifier ];
};
/**
* Returns one of the default shader programs
*
* @param gl
* @param shaderIdentifier
*
* @returns {*}
*/
x3dom.Cache.prototype.getShader = function ( gl, shaderIdentifier )
{
var program = null;
// Check if shader is in cache
if ( this.shaders[ shaderIdentifier ] === undefined )
{
// Choose shader based on identifier
switch ( shaderIdentifier )
{
case x3dom.shader.PICKING:
program = new x3dom.shader.PickingShader( gl );
break;
case x3dom.shader.PICKING_24:
program = new x3dom.shader.Picking24Shader( gl );
break;
case x3dom.shader.PICKING_ID:
program = new x3dom.shader.PickingIdShader( gl );
break;
case x3dom.shader.PICKING_COLOR:
program = new x3dom.shader.PickingColorShader( gl );
break;
case x3dom.shader.PICKING_TEXCOORD:
program = new x3dom.shader.PickingTexcoordShader( gl );
break;
case x3dom.shader.FRONTGROUND_TEXTURE:
program = new x3dom.shader.FrontgroundTextureShader( gl );
break;
case x3dom.shader.BACKGROUND_TEXTURE:
program = new x3dom.shader.BackgroundTextureShader( gl );
break;
case x3dom.shader.BACKGROUND_SKYTEXTURE:
program = new x3dom.shader.BackgroundSkyTextureShader( gl );
break;
case x3dom.shader.BACKGROUND_CUBETEXTURE:
program = new x3dom.shader.BackgroundCubeTextureShader( gl );
break;
case x3dom.shader.BACKGROUND_CUBETEXTURE_DDS:
program = new x3dom.shader.BackgroundCubeTextureDDSShader( gl );
break;
case x3dom.shader.SHADOW:
program = new x3dom.shader.ShadowShader( gl );
break;
case x3dom.shader.BLUR:
program = new x3dom.shader.BlurShader( gl );
break;
case x3dom.shader.DEPTH:
// program = new x3dom.shader.DepthShader(gl);
break;
case x3dom.shader.NORMAL:
program = new x3dom.shader.NormalShader( gl );
break;
case x3dom.shader.TEXTURE_REFINEMENT:
program = new x3dom.shader.TextureRefinementShader( gl );
break;
default:
break;
}
if ( program )
{
this.shaders[ shaderIdentifier ] = x3dom.Utils.wrapProgram( gl, program, shaderIdentifier );
}
else
{
x3dom.debug.logError( "Couldn't create shader: " + shaderIdentifier );
}
}
return this.shaders[ shaderIdentifier ];
};
/**
* Returns a dynamic generated shader program by viewarea and shape
*
* @param gl
* @param viewarea
* @param shape
*/
x3dom.Cache.prototype.getDynamicShader = function ( gl, viewarea, shape )
{
// Generate Properties
var properties = x3dom.Utils.generateProperties( viewarea, shape );
var shaderID = properties.id;
if ( this.shaders[ shaderID ] === undefined )
{
var program = null;
if ( properties.CSHADER != -1 )
{
program = new x3dom.shader.ComposedShader( gl, shape );
}
else
{
program = new x3dom.shader.DynamicShader( gl, properties );
}
this.shaders[ shaderID ] = x3dom.Utils.wrapProgram( gl, program, shaderID );
}
return this.shaders[ shaderID ];
};
/**
* Returns a dynamic generated shader program by properties
*
* @param gl
* @param shape
* @param properties
* @param pickMode
* @param shadows
*/
x3dom.Cache.prototype.getShaderByProperties = function ( gl, shape, properties, pickMode, shadows )
{
// Get shaderID
var shaderID = properties.id;
if ( pickMode !== undefined && pickMode !== null )
{
shaderID += pickMode;
}
if ( shadows !== undefined && shadows !== null )
{
shaderID += "S";
}
if ( this.shaders[ shaderID ] === undefined )
{
var program = null;
if ( pickMode !== undefined && pickMode !== null )
{
program = new x3dom.shader.DynamicShaderPicking( gl, properties, pickMode );
}
else if ( shadows !== undefined && shadows !== null )
{
program = new x3dom.shader.DynamicShadowShader( gl, properties );
}
else if ( properties.CSHADER != -1 )
{
program = new x3dom.shader.ComposedShader( gl, shape );
}
else if ( properties.KHR_MATERIAL_COMMONS != null && properties.KHR_MATERIAL_COMMONS != 0 )
{
program = new x3dom.shader.KHRMaterialCommonsShader( gl, properties );
}
else if ( properties.EMPTY_SHADER != null && properties.EMPTY_SHADER != 0 )
{
return { "shaderID": shaderID };
}
else
{
program = new x3dom.shader.DynamicShader( gl, properties );
}
this.shaders[ shaderID ] = x3dom.Utils.wrapProgram( gl, program, shaderID );
}
return this.shaders[ shaderID ];
};
/**
* Returns the dynamically created shadow rendering shader
*
* @param gl
* @param shadowedLights
*
* @returns {*}
*/
x3dom.Cache.prototype.getShadowRenderingShader = function ( gl, shadowedLights, properties )
{
var ID = "shadow" + Object.values( properties ).join( "" );
for ( var i = 0; i < shadowedLights.length; i++ )
{
if ( x3dom.isa( shadowedLights[ i ], x3dom.nodeTypes.SpotLight ) )
{
ID += "S";
}
else if ( x3dom.isa( shadowedLights[ i ], x3dom.nodeTypes.PointLight ) )
{
ID += "P";
}
else
{
ID += "D";
}
}
if ( this.shaders[ ID ] === undefined )
{
var program = new x3dom.shader.ShadowRenderingShader( gl, shadowedLights, properties );
this.shaders[ ID ] = x3dom.Utils.wrapProgram( gl, program, ID );
}
return this.shaders[ ID ];
};
/**
* Release texture and shader resources
*
* @param gl
* @constructor
*/
x3dom.Cache.prototype.Release = function ( gl )
{
for ( var texture in this.textures )
{
gl.deleteTexture( this.textures[ texture ] );
}
this.textures = [];
for ( var shaderId in this.shaders )
{
var shader = this.shaders[ shaderId ];
var glShaders = gl.getAttachedShaders( shader.program );
for ( var i = 0; i < glShaders.length; ++i )
{
gl.detachShader( shader.program, glShaders[ i ] );
gl.deleteShader( glShaders[ i ] );
}
gl.deleteProgram( shader.program );
}
this.shaders = [];
};
/**
* X3DOM JavaScript Library
* http://www.x3dom.org
*
* (C)2009 Fraunhofer IGD, Darmstadt, Germany
* Dual licensed under the MIT and GPL
*
* Based on code originally provided by
* Philip Taylor: http://philip.html5.org
*/
/**
* Start Dash Video
*
* @param recurl
* @param texturediv
*/
function startDashVideo ( recurl, texturediv )
{
var vars = function ()
{
var vars = {};
var parts = window.location.href.replace( /[?&]+([^=&]+)=([^&]*)/gi, function ( m, key, value )
{
vars[ key ] = value;
} );
return vars;
},
url = recurl,
video,
context,
player;
if ( vars && vars.hasOwnProperty( "url" ) )
{
url = vars.url;
}
video = document.querySelector( texturediv );
context = new Dash.di.DashContext();
player = new MediaPlayer( context );
player.startup();
player.attachView( video );
player.setAutoPlay( false );
player.attachSource( url );
}
/**
* Texture
*
* @param gl
* @param doc
* @param cache
* @param node
* @constructor
*/
x3dom.Texture = function ( gl, doc, cache, node )
{
this.gl = gl;
this.doc = doc;
this.cache = cache;
this.node = node;
this.samplerName = "diffuseMap";
this.type = gl.TEXTURE_2D;
this.format = gl.RGBA;
this.magFilter = gl.LINEAR;
this.minFilter = gl.LINEAR;
this.wrapS = gl.REPEAT;
this.wrapT = gl.REPEAT;
this.genMipMaps = false;
this.texture = null;
this.ready = false;
this.anisotropicDegree = 1.0;
this.dashtexture = false;
var tex = this.node;
var suffix = "mpd";
this.node._x3domTexture = this;
if ( x3dom.isa( tex, x3dom.nodeTypes.MovieTexture ) )
{
// for dash we are lazy and check only the first url
if ( tex._vf.url[ 0 ].indexOf( suffix, tex._vf.url[ 0 ].length - suffix.length ) !== -1 )
{
this.dashtexture = true;
// we need to initially place the script for the dash player once in the document,
// but insert this additional script only, if really needed and Dash is requested!
var js = document.getElementById( "AdditionalDashVideoScript" );
if ( !js )
{
js = document.createElement( "script" );
js.setAttribute( "type", "text/javascript" );
js.setAttribute( "src", x3dom.Texture.dashVideoScriptFile );
js.setAttribute( "id", "AdditionalDashVideoScript" );
js.onload = function ()
{
var texObj;
while ( ( texObj = x3dom.Texture.loadDashVideos.pop() ) )
{
x3dom.Texture.textNum++;
texObj.update();
}
js.ready = true;
};
document.getElementsByTagName( "head" )[ 0 ].appendChild( js );
}
if ( js.ready === true )
{
// count dash players and add this number to the class name for future reference
// (append in id too, for play, pause etc)
x3dom.Texture.textNum++;
// update can be directly called as script is already loaded
this.update();
}
else
{
// push to stack and process later when script has loaded
x3dom.Texture.loadDashVideos.push( this );
}
}
}
if ( !this.dashtexture )
{
this.update();
}
};
x3dom.Texture.dashVideoScriptFile = "dash.all.js";
x3dom.Texture.loadDashVideos = [];
x3dom.Texture.textNum = 0;
x3dom.Texture.clampFontSize = false;
x3dom.Texture.minFontQuality = 0.5;
x3dom.Texture.maxFontQuality = 10;
/**
* Update
*
*/
x3dom.Texture.prototype.update = function ()
{
if ( x3dom.isa( this.node, x3dom.nodeTypes.Text ) )
{
this.updateText();
}
else
{
this.updateTexture();
}
this.node.validateGLObject();
};
/**
* Set Pixel
*
* @param x
* @param y
* @param pixel
* @param update
*/
x3dom.Texture.prototype.setPixel = function ( x, y, pixel, update )
{
var gl = this.gl;
var pixels = new Uint8Array( pixel );
gl.bindTexture( this.type, this.texture );
gl.pixelStorei( gl.UNPACK_ALIGNMENT, 1 );
gl.texSubImage2D( this.type, 0, x, y, 1, 1, this.format, gl.UNSIGNED_BYTE, pixels );
gl.bindTexture( this.type, null );
if ( update )
{
this.doc.needRender = true;
}
};
/**
* Update Texture
*
*/
x3dom.Texture.prototype.updateTexture = function ()
{
var gl = this.gl;
var doc = this.doc;
var tex = this.node;
// Set sampler
this.samplerName = tex._type;
// Set texture type
if ( x3dom.isa( tex, x3dom.nodeTypes.X3DEnvironmentTextureNode ) )
{
this.type = gl.TEXTURE_CUBE_MAP;
}
else
{
this.type = gl.TEXTURE_2D;
}
// Set texture format
if ( x3dom.isa( tex, x3dom.nodeTypes.PixelTexture ) )
{
switch ( tex._vf.image.comp )
{
case 1:
this.format = gl.LUMINANCE;
break;
case 2:
this.format = gl.LUMINANCE_ALPHA;
break;
case 3:
this.format = gl.RGB;
break;
case 4:
this.format = gl.RGBA;
break;
}
}
else
{
this.format = gl.RGBA;
}
// Set texture min, mag, wrapS and wrapT
if ( tex._cf.textureProperties.node !== null )
{
var texProp = tex._cf.textureProperties.node;
this.wrapS = x3dom.Utils.boundaryModesDic( gl, texProp._vf.boundaryModeS );
this.wrapT = x3dom.Utils.boundaryModesDic( gl, texProp._vf.boundaryModeT );
this.minFilter = x3dom.Utils.minFilterDic( gl, texProp._vf.minificationFilter );
this.magFilter = x3dom.Utils.magFilterDic( gl, texProp._vf.magnificationFilter );
this.anisotropicDegree = Math.min( Math.max( texProp._vf.anisotropicDegree, 1.0 ), x3dom.caps.MAX_ANISOTROPY );
if ( texProp._vf.generateMipMaps === true )
{
this.genMipMaps = true;
if ( this.minFilter == gl.NEAREST )
{
this.minFilter = gl.NEAREST_MIPMAP_NEAREST;
}
else if ( this.minFilter == gl.LINEAR )
{
this.minFilter = gl.LINEAR_MIPMAP_LINEAR;
}
if ( this.texture && ( this.texture.ready || this.texture.textureCubeReady ) )
{
gl.bindTexture( this.type, this.texture );
gl.generateMipmap( this.type );
gl.bindTexture( this.type, null );
}
}
else
{
this.genMipMaps = false;
if ( ( this.minFilter == gl.LINEAR_MIPMAP_LINEAR ) ||
( this.minFilter == gl.LINEAR_MIPMAP_NEAREST ) )
{
this.minFilter = gl.LINEAR;
}
else if ( ( this.minFilter == gl.NEAREST_MIPMAP_LINEAR ) ||
( this.minFilter == gl.NEAREST_MIPMAP_NEAREST ) )
{
this.minFilter = gl.NEAREST;
}
}
}
else
{
if ( tex._vf.repeatS == false )
{
this.wrapS = gl.CLAMP_TO_EDGE;
}
else
{
this.wrapS = gl.REPEAT;
}
if ( tex._vf.repeatT == false )
{
this.wrapT = gl.CLAMP_TO_EDGE;
}
else
{
this.wrapT = gl.REPEAT;
}
if ( this.samplerName == "displacementMap" )
{
this.wrapS = gl.CLAMP_TO_EDGE;
this.wrapT = gl.CLAMP_TO_EDGE;
this.minFilter = gl.NEAREST;
this.magFilter = gl.NEAREST;
}
}
// Looking for child texture
var childTex = ( tex._video && tex._needPerFrameUpdate === true );
// Set texture
if ( tex._isCanvas && tex._canvas )
{
if ( this.texture == null )
{
this.texture = gl.createTexture();
}
this.texture.width = tex._canvas.width;
this.texture.height = tex._canvas.height;
this.texture.ready = true;
gl.bindTexture( this.type, this.texture );
gl.texImage2D( this.type, 0, this.format, this.format, gl.UNSIGNED_BYTE, tex._canvas );
if ( this.genMipMaps )
{
gl.generateMipmap( this.type );
}
gl.bindTexture( this.type, null );
}
else if ( x3dom.isa( tex, x3dom.nodeTypes.RenderedTexture ) )
{
if ( tex._webgl && tex._webgl.fbo )
{
if ( tex._webgl.fbo.dtex && tex._vf.depthMap )
{
this.texture = tex._webgl.fbo.dtex;
}
else
{
this.texture = tex._webgl.fbo.tex;
}
}
else
{
this.texture = null;
x3dom.debug.logError( "Try updating RenderedTexture without FBO initialized!" );
}
if ( this.texture )
{
this.texture.ready = true;
}
}
else if ( x3dom.isa( tex, x3dom.nodeTypes.PixelTexture ) )
{
if ( tex._vf.origChannelCount == 0 ) {tex.setOrigChannelCount( tex._vf.image.comp );}
if ( this.texture == null )
{
if ( this.node._DEF )
{
this.texture = this.cache.getTexture2DByDEF( gl, this.node._nameSpace, this.node._DEF );
}
else
{
this.texture = gl.createTexture();
}
}
this.texture.width = tex._vf.image.width;
this.texture.height = tex._vf.image.height;
this.texture.ready = true;
var pixelArr = tex._vf.image.array;
var pixelArrfont_size = tex._vf.image.width * tex._vf.image.height * tex._vf.image.comp;
var pixels = new Uint8Array( pixelArrfont_size );
pixels.set( pixelArr );
gl.bindTexture( this.type, this.texture );
gl.pixelStorei( gl.UNPACK_ALIGNMENT, 1 );
gl.texImage2D( this.type, 0, this.format,
tex._vf.image.width, tex._vf.image.height, 0,
this.format, gl.UNSIGNED_BYTE, pixels );
if ( this.genMipMaps )
{
gl.generateMipmap( this.type );
}
gl.bindTexture( this.type, null );
}
else if ( x3dom.isa( tex, x3dom.nodeTypes.MovieTexture ) || childTex )
{
var that = this;
var p = document.getElementsByTagName( "body" )[ 0 ];
if ( this.texture == null )
{
this.texture = gl.createTexture();
}
if ( this.dashtexture )
{
var element_vid = document.createElement( "div" );
element_vid.setAttribute( "class", "dash-video-player" + x3dom.Texture.textNum );
tex._video = document.createElement( "video" );
tex._video.setAttribute( "preload", "auto" );
tex._video.setAttribute( "muted", "muted" );
var scriptToRun = document.createElement( "script" );
scriptToRun.setAttribute( "type", "text/javascript" );
scriptToRun.innerHTML = "startDashVideo(\"" + tex._vf.url[ 0 ] +
"\",\".dash-video-player" + x3dom.Texture.textNum + " video\")";
element_vid.appendChild( scriptToRun );
element_vid.appendChild( tex._video );
p.appendChild( element_vid );
tex._video.style.visibility = "hidden";
tex._video.style.display = "none";
}
else
{
if ( !childTex )
{
tex._video = document.createElement( "video" );
tex._video.setAttribute( "preload", "auto" );
tex._video.setAttribute( "muted", "muted" );
tex._video.setAttribute( "autoplay", "" );
tex._video.setAttribute( "playsinline", "" );
tex._video.crossOrigin = "anonymous";
// p.appendChild( tex._video );
// tex._video.style.visibility = "hidden";
// tex._video.style.display = "none";
tex._video.load();
}
for ( var i = 0; i < tex._vf.url.length; i++ )
{
var videoUrl = tex._nameSpace.getURL( tex._vf.url[ i ] );
x3dom.debug.logInfo( "Adding video file: " + videoUrl );
var src = document.createElement( "source" );
src.setAttribute( "src", videoUrl );
tex._video.appendChild( src );
}
}
var updateMovie = function ()
{
gl.bindTexture( that.type, that.texture );
gl.texImage2D( that.type, 0, that.format, that.format, gl.UNSIGNED_BYTE, tex._video );
if ( that.genMipMaps )
{
gl.generateMipmap( that.type );
}
gl.bindTexture( that.type, null );
that.texture.ready = true;
doc.needRender = true;
};
var startVideo = function ()
{
//x3dom.debug.logInfo( "startVideo" );
window.removeEventListener( "mousedown", startVideo );
window.removeEventListener( "keydown", startVideo );
if ( !( tex._video instanceof HTMLMediaElement ) )
{
x3dom.debug.logInfo( "No video exists." );
return;
}
tex._video.play()
.then( function fulfilled ()
{
if ( tex._intervalID )
{
x3dom.debug.logInfo( "The video has already started, startVideo() is called repeatedly." );
clearInterval( tex._intervalID );
tex._intervalID = null;
}
tex._intervalID = setInterval( updateMovie, 16 );
} )
.catch( function rejected ( err )
{
x3dom.debug.logInfo( "Waiting for interaction: " + err );
window.addEventListener( "mousedown", startVideo );
window.addEventListener( "keydown", startVideo );
} );
};
var pauseVideo = function ()
{
//x3dom.debug.logInfo( "pauseVideo" );
window.removeEventListener( "mousedown", startVideo );
window.removeEventListener( "keydown", startVideo );
tex._video.pause();
clearInterval( tex._intervalID );
tex._intervalID = null;
};
var videoDone = function ()
{
clearInterval( tex._intervalID );
tex._intervalID = null;
if ( tex._vf.loop === true )
{
tex._video.play();
tex._intervalID = setInterval( updateMovie, 16 );
}
};
tex._video.startVideo = startVideo;
tex._video.pauseVideo = pauseVideo;
// Start listening for the canplaythrough event, so we do not
// start playing the video until we can do so without stuttering
tex._video.addEventListener( "canplaythrough", startVideo, true );
// Start listening for the ended event, so we can stop the
// texture update when the video is finished playing
tex._video.addEventListener( "ended", videoDone, true );
}
else if ( x3dom.isa( tex, x3dom.nodeTypes.X3DEnvironmentTextureNode ) )
{
this.texture = this.cache.getTextureCube( gl, doc, tex.getTexUrl(), false,
tex._vf.crossOrigin, tex._vf.scale, this.genMipMaps, tex._vf.flipY );
}
else
{
this.texture = this.cache.getTexture2D( gl, doc, tex._nameSpace.getURL( tex._vf.url[ 0 ] ),
false, tex._vf.crossOrigin, tex._vf.scale, this.genMipMaps, tex._vf.flipY, tex );
}
};
/**
* Update Text
*
*/
x3dom.Texture.prototype.updateText = function ()
{
var gl = this.gl;
this.wrapS = gl.CLAMP_TO_EDGE;
this.wrapT = gl.CLAMP_TO_EDGE;
this.type = gl.TEXTURE_2D;
this.format = gl.RGBA;
this.magFilter = gl.LINEAR;
this.minFilter = gl.LINEAR_MIPMAP_LINEAR;
this.genMipMaps = true;
if ( x3dom.caps.MAX_ANISOTROPY )
{
this.anisotropicDegree = x3dom.caps.MAX_ANISOTROPY;
}
var fontStyleNode = this.node._cf.fontStyle.node, // should always exist?
font_family = "serif", // should be dealt with by default fontStyleNode?
font_style = "normal",
font_justify = "left",
font_size = 1.0,
font_spacing = 1.0,
font_horizontal = true,
font_language = "",
oversample = 2.0,
minor_alignment = "FIRST";
if ( fontStyleNode !== null )
{
var fonts = fontStyleNode._vf.family.toString();
// clean attribute values and split in array
fonts = fonts.trim().replace( /\'/g, "" ).replace( /\,/, " " );
fonts = fonts.split( " " );
font_family = fonts.map( function ( s )
{
if ( s == "SANS" )
{
return "Verdana, sans-serif";
}
else if ( s == "SERIF" )
{
return "Georgia, serif";
}
else if ( s == "TYPEWRITER" )
{
return "monospace";
}
else
{
return "" + s + "";
} // 'Verdana'
} ).join( "," );
font_style = fontStyleNode._vf.style.toString().replace( /\'/g, "" );
switch ( font_style.toUpperCase() )
{
case "PLAIN":
font_style = "normal";
break;
case "BOLD":
font_style = "bold";
break;
case "ITALIC":
font_style = "italic";
break;
case "BOLDITALIC":
font_style = "italic bold";
break;
default:
font_style = "normal";
}
var leftToRight = fontStyleNode._vf.leftToRight ? "ltr" : "rtl";
var topToBottom = fontStyleNode._vf.topToBottom;
var beginAlign = leftToRight == "ltr" ? "left" : "right";
var endAlign = leftToRight == "ltr" ? "right" : "left";
font_justify = fontStyleNode._vf.justify[ 0 ].toString().replace( /\'/g, "" );
switch ( font_justify.toUpperCase() )
{
case "BEGIN":
font_justify = beginAlign;
break;
case "END":
font_justify = endAlign;
break;
case "FIRST":
font_justify = beginAlign;
break; // relevant only in justify[1], eg. minor alignment
case "MIDDLE":
font_justify = "center";
break;
default:
font_justify = beginAlign;
break;
}
if ( fontStyleNode._vf.justify[ 1 ] === undefined )
{
minor_alignment = "FIRST";
}
else
{
minor_alignment = fontStyleNode._vf.justify[ 1 ].toString().replace( /\'/g, "" );
switch ( minor_alignment.toUpperCase() )
{
case "BEGIN":
minor_alignment = "BEGIN";
break;
case "FIRST":
minor_alignment = "FIRST";
break;
case "MIDDLE":
minor_alignment = "MIDDLE";
break;
case "END":
minor_alignment = "END";
break;
default:
minor_alignment = "FIRST";
break;
}
}
font_size = fontStyleNode._vf.size;
font_spacing = fontStyleNode._vf.spacing;
font_horizontal = fontStyleNode._vf.horizontal; //TODO: vertical needs canvas support
font_language = fontStyleNode._vf.language;
oversample = fontStyleNode._vf.quality;
oversample = Math.max( x3dom.Texture.minFontQuality, oversample );
oversample = Math.min( x3dom.Texture.maxFontQuality, oversample );
if ( font_size < 0.1 ) {font_size = 0.1;}
if ( x3dom.Texture.clampFontSize && font_size > 2.3 )
{
font_size = 2.3;
}
}
var textX,
textY,
paragraph = this.node._vf.string,
maxExtent = this.node._vf.maxExtent,
lengths = [],
x3dToPx = 32,
textAlignment = font_justify;
var text_canvas = document.createElement( "canvas" );
text_canvas.dir = leftToRight;
var textHeight = font_size * x3dToPx; // pixel size relative to local coordinate system
// needed to make webfonts work
document.body.appendChild( text_canvas );
var text_ctx = text_canvas.getContext( "2d" );
text_ctx.font = font_style + " " + textHeight + "px " + font_family;
var maxWidth = 0,
pWidth,
pLength,
i,
j;
// calculate maxWidth and length scaling; sanitize lengths
for ( i = 0; i < paragraph.length; i++ )
{
pWidth = text_ctx.measureText( paragraph[ i ] ).width;
if ( pWidth > maxWidth )
{
maxWidth = pWidth;
}
pLength = this.node._vf.length[ i ] | 0;
if ( maxExtent > 0 && ( pLength > maxExtent || pLength == 0 ) )
{
pLength = maxExtent;
}
lengths[ i ] = pLength <= 0 ? pWidth : pLength * x3dToPx;
}
var canvas_extra = 0.25 * textHeight; // single line, some fonts are higher than textHeight
var txtW = maxWidth + canvas_extra; // needed for italic since textMetrics.width ignores that
var txtH = textHeight + textHeight * font_spacing * ( paragraph.length - 1 ) + canvas_extra;
textX = 0;
textY = 0;
var x_offset = 0,
y_offset = 0,
baseLine = "alphabetic";
// x_offset and starting X
switch ( font_justify )
{
case "center":
x_offset = -txtW / 2;
textX = txtW / 2;
break;
case "left":
x_offset = 0;
textX = 0;
break;
case "right":
x_offset = -txtW;
textX = txtW;
break;
}
// y_offset, baseline and first Y
switch ( minor_alignment )
{
case "MIDDLE":
y_offset = txtH / 2 - canvas_extra / 2;
baseLine = "middle";
textY = topToBottom ? textHeight / 2 : textHeight / 2;
break;
case "BEGIN":
y_offset = topToBottom ? 0 : txtH - canvas_extra;
baseLine = topToBottom ? "top" : "bottom";
textY = topToBottom ? 0 : textHeight; // adjust for baseline
break;
case "FIRST":
//special case of BEGIN
y_offset = topToBottom ? textHeight : txtH - canvas_extra;
baseLine = topToBottom ? "alphabetic" : "bottom";
textY = topToBottom ? textHeight : textHeight;
break;
case "END":
y_offset = topToBottom ? txtH - canvas_extra : 0;
baseLine = topToBottom ? "bottom" : "top";
textY = topToBottom ? textHeight : 0;
break;
}
var pxToX3d = 1 / x3dToPx;
var w = txtW * pxToX3d;
var h = txtH * pxToX3d;
var max_texture_size = x3dom.caps.MAX_TEXTURE_SIZE >> 2;
x_offset *= pxToX3d;
y_offset *= pxToX3d;
text_canvas.width = Math.min(
x3dom.Utils.nextHighestPowerOfTwo( txtW * oversample ),
max_texture_size );
text_canvas.height = Math.min(
x3dom.Utils.nextHighestPowerOfTwo( txtH * oversample ),
max_texture_size );
text_canvas.dir = leftToRight;
text_ctx.scale( text_canvas.width / txtW, text_canvas.height / txtH );
// transparent background
text_ctx.fillStyle = "rgba(0,0,0,0)";
text_ctx.fillRect( 0, 0, text_ctx.canvas.width, text_ctx.canvas.height );
// write white text with black border
text_ctx.fillStyle = "white";
text_ctx.textBaseline = baseLine;
text_ctx.font = font_style + " " + textHeight + "px " + font_family;
text_ctx.textAlign = textAlignment;
var renderConfig = {
font_style : font_style,
font_family : font_family,
font_spacing : font_spacing,
paragraph : paragraph,
topToBottom : topToBottom,
leftToRight : leftToRight,
textX : textX,
textY : textY,
textHeight : textHeight,
lengths : lengths
};
this.renderScaledText( text_ctx, 1, renderConfig );
if ( this.texture === null )
{
this.texture = gl.createTexture();
}
gl.bindTexture( this.type, this.texture );
this.uploadTextMipmap( text_canvas, renderConfig );
gl.bindTexture( this.type, null );
//remove canvas after Texture creation
document.body.removeChild( text_canvas );
this.node._mesh._positions[ 0 ] = [
0 + x_offset, -h + y_offset, 0,
w + x_offset, -h + y_offset, 0,
w + x_offset, 0 + y_offset, 0,
0 + x_offset, 0 + y_offset, 0 ];
this.node.invalidateVolume();
this.node._parentNodes.forEach( function ( node )
{
node.setAllDirty();
} );
};
x3dom.Texture.prototype.renderScaledText = function ( ctx2d, pot, txt )
{
ctx2d.font = txt.font_style + " " + txt.textHeight / pot + "px " + txt.font_family;
var textYpos = txt.textY;
// create the multiline text always top down
for ( var i = 0; i < txt.paragraph.length; i++ )
{
var j = txt.topToBottom ? i : txt.paragraph.length - 1 - i;
var paragraphj = txt.paragraph[ j ];
if ( txt.leftToRight == "rtl" ) {paragraphj = "\u202e" + paragraphj;} //force rtl unicode mark
ctx2d.fillText( paragraphj, txt.textX / pot, textYpos / pot, txt.lengths[ j ] / pot );
textYpos += txt.textHeight * txt.font_spacing;
}
};
x3dom.Texture.prototype.uploadTextMipmap = function ( canvas, txt )
{
var gl = this.gl,
w = canvas.width,
h = canvas.height,
level = 0,
pot = 1,
w2 = w,
h2 = h,
ctx2d = canvas.getContext( "2d" );
while ( true )
{
gl.texImage2D( this.type, level++, this.format, this.format, gl.UNSIGNED_BYTE, ctx2d.getImageData( 0, 0, w2, h2 ) );
if ( w2 == 1 && h2 == 1 ) {break;}
w2 = Math.max( 1, w2 >> 1 );
h2 = Math.max( 1, h2 >> 1 );
ctx2d.clearRect( 0, 0, w, h );
pot *= 2;
this.renderScaledText( ctx2d, pot, txt );
}
// this.gl.generateMipmap( this.type );
};
/*
* X3DOM JavaScript Library
* http://www.x3dom.org
*
* (C)2009 Fraunhofer IGD, Darmstadt, Germany
* Dual licensed under the MIT and GPL
*
* Based on code originally provided by
* Philip Taylor: http://philip.html5.org
*/
// ### X3DDocument ###
x3dom.X3DDocument = function ( canvas, ctx, settings )
{
this.canvas = canvas; // The <canvas> elem
this.ctx = ctx; // WebGL context object, AKA gl
this.properties = settings; // showStat, showLog, etc.
this.needRender = true; // Trigger redraw if true
this._x3dElem = null; // Backref to <X3D> root element (set on parsing)
this._scene = null; // Scene root element
this._viewarea = null; // Viewport, handles rendering and interaction
this.downloadCount = 0; // Counter for objects to be loaded
this.previousDownloadCount = 0;
this.mutationObserver = new MutationObserver( this.onMutation.bind( this ) );
this.X3DMutationObserver = new MutationObserver( this.onX3DMutation.bind( this ) );
// bag for pro-active (or multi-core-like) elements
this._nodeBag = {
timer : [], // TimeSensor (tick)
lights : [], // Light
clipPlanes : [], // ClipPlane
followers : [], // X3DFollowerNode
trans : [], // X3DTransformNode (for listening to CSS changes)
renderTextures : [], // RenderedTexture
viewarea : [], // Viewport (for updating camera navigation)
affectedPointingSensors : [] // all X3DPointingDeviceSensor currently activated (i.e., used for interaction),
// this list is maintained for efficient update / deactivation
};
this.onload = function () {};
this.onerror = function () {};
};
x3dom.X3DDocument.prototype.load = function ( uri, sceneElemPos )
{
// Load uri. Get sceneDoc, list of sub-URIs.
// For each URI, get docs[uri] = whatever, extend list of sub-URIs.
var uri_docs = {};
var queued_uris = [ uri ];
var doc = this;
function next_step ()
{
// TODO: detect circular inclusions
// TODO: download in parallel where possible
if ( queued_uris.length === 0 )
{
// All done
doc._setup( uri_docs[ uri ], uri_docs, sceneElemPos );
doc.onload();
return;
}
var next_uri = queued_uris.shift();
if ( x3dom.isX3DElement( next_uri ) &&
( next_uri.localName.toLowerCase() === "x3d" || next_uri.localName.toLowerCase() === "websg" ) )
{
// Special case, when passed an X3D node instead of a URI string
uri_docs[ next_uri ] = next_uri;
doc._x3dElem = next_uri;
next_step();
}
}
next_step();
};
x3dom.findScene = function ( x3dElem )
{
var sceneElems = [];
for ( var i = 0; i < x3dElem.childNodes.length; i++ )
{
var sceneElem = x3dElem.childNodes[ i ];
if ( sceneElem && sceneElem.localName && sceneElem.localName.toLowerCase() === "scene" )
{
sceneElems.push( sceneElem );
}
}
if ( sceneElems.length > 1 )
{
x3dom.debug.logError( "X3D element has more than one Scene child (has " +
x3dElem.childNodes.length + ")." );
}
else
{
return sceneElems[ 0 ];
}
return null;
};
x3dom.X3DDocument.prototype._setup = function ( sceneDoc )
{
var doc = this;
// sceneDoc is the X3D element here...
var sceneElem = x3dom.findScene( sceneDoc );
this.X3DMutationObserver.observe( document, { attributes: false, attributeOldValue: false, childList: true, subtree: true } );
this.mutationObserver.observe( sceneDoc, { attributes: false, attributeOldValue: false, childList: true, subtree: false } );
this.mutationObserver.observe( sceneElem, { attributes: true, attributeOldValue: true, childList: true, subtree: true } );
// create and add BindableBag that holds all bindable stacks
this._bindableBag = new x3dom.BindableBag( this );
// create and add the NodeNameSpace
var nameSpace = new x3dom.NodeNameSpace( "scene", doc );
var scene = nameSpace.setupTree( sceneElem );
// link scene
this._scene = scene;
this._bindableBag.setRefNode( scene );
// create view
this._viewarea = new x3dom.Viewarea( this, scene );
this._viewarea._width = this.canvas.width;
this._viewarea._height = this.canvas.height;
};
x3dom.X3DDocument.prototype.advanceTime = function ( t )
{
var i = 0;
if ( this._nodeBag.timer.length )
{
for ( i = 0; i < this._nodeBag.timer.length; i++ )
{ this.needRender |= this._nodeBag.timer[ i ].tick( t ); }
}
if ( this._nodeBag.followers.length )
{
for ( i = 0; i < this._nodeBag.followers.length; i++ )
{ this.needRender |= this._nodeBag.followers[ i ].tick( t ); }
}
// just a temporary tricker solution to update the CSS transforms
if ( this._nodeBag.trans.length )
{
for ( i = 0; i < this._nodeBag.trans.length; i++ )
{ this.needRender |= this._nodeBag.trans[ i ].tick( t ); }
}
if ( this._nodeBag.viewarea.length )
{
for ( i = 0; i < this._nodeBag.viewarea.length; i++ )
{ this.needRender |= this._nodeBag.viewarea[ i ].tick( t ); }
}
};
x3dom.X3DDocument.prototype.render = function ( ctx, frameData )
{
if ( !ctx || !this._viewarea )
{
return;
}
this._viewarea.setVRFrameData( ctx, frameData );
this._viewarea.updateGamepads( frameData );
ctx.renderScene( this._viewarea, frameData );
};
x3dom.X3DDocument.prototype.onPick = function ( ctx, x, y )
{
if ( !ctx || !this._viewarea )
{
return;
}
ctx.pickValue( this._viewarea, x, y, 1 );
};
x3dom.X3DDocument.prototype.onPickRect = function ( ctx, x1, y1, x2, y2 )
{
if ( !ctx || !this._viewarea )
{
return [];
}
return ctx.pickRect( this._viewarea, x1, y1, x2, y2 );
};
x3dom.X3DDocument.prototype.onMove = function ( ctx, x, y, buttonState )
{
if ( !ctx || !this._viewarea )
{
return;
}
if ( this._viewarea._scene._vf.doPickPass )
{ctx.pickValue( this._viewarea, x, y, buttonState );}
this._viewarea.onMove( x, y, buttonState );
};
x3dom.X3DDocument.prototype.onMoveView = function ( ctx, evt, touches, translation, rotation )
{
if ( !ctx || !this._viewarea )
{
return;
}
this._scene.getNavigationInfo()._impl.onTouchDrag( this._viewarea, evt, touches, translation, rotation );
};
x3dom.X3DDocument.prototype.onDrag = function ( ctx, x, y, buttonState )
{
if ( !ctx || !this._viewarea )
{
return;
}
if ( this._viewarea._scene._vf.doPickPass && !this._viewarea._isMoving )
{ctx.pickValue( this._viewarea, x, y, buttonState );}
this._viewarea.onDrag( x, y, buttonState );
};
x3dom.X3DDocument.prototype.onWheel = function ( ctx, x, y, originalY )
{
if ( !ctx || !this._viewarea )
{
return;
}
if ( this._viewarea._scene._vf.doPickPass )
{ctx.pickValue( this._viewarea, x, originalY, 0 );}
this._viewarea.onDrag( x, y, 2 );
};
x3dom.X3DDocument.prototype.onMousePress = function ( ctx, x, y, buttonState )
{
if ( !ctx || !this._viewarea )
{
return;
}
// update volume only on click since expensive!
this._viewarea._scene.updateVolume();
ctx.pickValue( this._viewarea, x, y, buttonState );
this._viewarea.onMousePress( x, y, buttonState );
};
x3dom.X3DDocument.prototype.onMouseRelease = function ( ctx, x, y, buttonState, prevButton )
{
if ( !ctx || !this._viewarea )
{
return;
}
var button = ( prevButton << 8 ) | buttonState; // for shadowObjectIdChanged
ctx.pickValue( this._viewarea, x, y, button );
this._viewarea.onMouseRelease( x, y, buttonState, prevButton );
};
x3dom.X3DDocument.prototype.onMouseOver = function ( ctx, x, y, buttonState )
{
if ( !ctx || !this._viewarea )
{
return;
}
ctx.pickValue( this._viewarea, x, y, buttonState );
this._viewarea.onMouseOver( x, y, buttonState );
};
x3dom.X3DDocument.prototype.onMouseOut = function ( ctx, x, y, buttonState )
{
if ( !ctx || !this._viewarea )
{
return;
}
ctx.pickValue( this._viewarea, x, y, buttonState );
this._viewarea.onMouseOut( x, y, buttonState );
};
x3dom.X3DDocument.prototype.onDoubleClick = function ( ctx, x, y )
{
if ( !ctx || !this._viewarea )
{
return;
}
this._viewarea.onDoubleClick( x, y );
};
x3dom.X3DDocument.prototype.onKeyDown = function ( keyCode )
{
//x3dom.debug.logInfo("pressed key " + keyCode);
switch ( keyCode )
{
case 37: /* left */
this._viewarea.strafeLeft();
break;
case 38: /* up */
this._viewarea.moveFwd();
break;
case 39: /* right */
this._viewarea.strafeRight();
break;
case 40: /* down */
this._viewarea.moveBwd();
break;
default:
}
};
x3dom.X3DDocument.prototype.onKeyUp = function ( keyCode )
{
//x3dom.debug.logInfo("released key " + keyCode);
var stack = null;
switch ( keyCode )
{
case 13: /* return */
x3dom.toggleFullScreen();
break;
case 33: /* page up */
stack = this._scene.getViewpoint()._stack;
if ( stack )
{
stack.switchTo( "prev" );
}
else
{
x3dom.debug.logError( "No valid ViewBindable stack." );
}
break;
case 34: /* page down */
stack = this._scene.getViewpoint()._stack;
if ( stack )
{
stack.switchTo( "next" );
}
else
{
x3dom.debug.logError( "No valid ViewBindable stack." );
}
break;
case 35: /* end */
stack = this._scene.getViewpoint()._stack;
if ( stack )
{
stack.switchTo( "last" );
}
else
{
x3dom.debug.logError( "No valid ViewBindable stack." );
}
break;
case 36: /* home */
stack = this._scene.getViewpoint()._stack;
if ( stack )
{
stack.switchTo( "first" );
}
else
{
x3dom.debug.logError( "No valid ViewBindable stack." );
}
break;
case 37: /* left */
break;
case 38: /* up */
break;
case 39: /* right */
break;
case 40: /* down */
break;
default:
}
};
x3dom.X3DDocument.prototype.onKeyPress = function ( charCode )
{
//x3dom.debug.logInfo("pressed key " + charCode);
var nav = this._scene.getNavigationInfo();
var env = this._scene.getEnvironment();
switch ( charCode )
{
case 32: /* space */
var states = this.canvas.parent.stateViewer;
if ( states )
{
states.display();
}
x3dom.debug.logInfo( "a: show all | i: fit view | d: show helper buffers | s: small feature culling | t: light view | " +
"m: toggle render mode | c: frustum culling | p: intersect type | \n" +
"e: examine mode | f: fly mode | y: freefly mode | w: walk mode | h: helicopter mode | " +
"l: lookAt mode | o: lookaround | g: game mode | n: turntable | u: upright position | \n" +
"v: print viewpoint info | r: reset view | home: first view | end: last view | pageUp: next view | pageDown: prev. view | " +
"+: increase speed | -: decrease speed " );
break;
case 43: /* + (incr. speed) */
nav._vf.speed = 2 * nav._vf.speed;
x3dom.debug.logInfo( "Changed navigation speed to " + nav._vf.speed );
break;
case 45: /* - (decr. speed) */
nav._vf.speed = 0.5 * nav._vf.speed;
x3dom.debug.logInfo( "Changed navigation speed to " + nav._vf.speed );
break;
case 51: /* 3 (decr pg error tol) */
x3dom.nodeTypes.PopGeometry.ErrorToleranceFactor += 0.5;
x3dom.debug.logInfo( "Changed POP error tolerance to " + x3dom.nodeTypes.PopGeometry.ErrorToleranceFactor );
break;
case 52: /* 4 (incr pg error tol) */
x3dom.nodeTypes.PopGeometry.ErrorToleranceFactor -= 0.5;
x3dom.debug.logInfo( "Changed POP error tolerance to " + x3dom.nodeTypes.PopGeometry.ErrorToleranceFactor );
break;
case 54: /* 6 (incr height) */
nav._vf.typeParams[ 1 ] += 1.0;
nav._heliUpdated = false;
x3dom.debug.logInfo( "Changed helicopter height to " + nav._vf.typeParams[ 1 ] );
break;
case 55: /* 7 (decr height) */
nav._vf.typeParams[ 1 ] -= 1.0;
nav._heliUpdated = false;
x3dom.debug.logInfo( "Changed helicopter height to " + nav._vf.typeParams[ 1 ] );
break;
case 56: /* 8 (decr angle) */
nav._vf.typeParams[ 0 ] -= 0.02;
nav._heliUpdated = false;
x3dom.debug.logInfo( "Changed helicopter angle to " + nav._vf.typeParams[ 0 ] );
break;
case 57: /* 9 (incr angle) */
nav._vf.typeParams[ 0 ] += 0.02;
nav._heliUpdated = false;
x3dom.debug.logInfo( "Changed helicopter angle to " + nav._vf.typeParams[ 0 ] );
break;
case 97: /* a, view all */
this._viewarea.showAll();
break;
case 99: /* c, toggle frustum culling */
env._vf.frustumCulling = !env._vf.frustumCulling;
x3dom.debug.logInfo( "Viewfrustum culling " + ( env._vf.frustumCulling ? "on" : "off" ) );
break;
case 100: /* d, switch on/off buffer view for dbg */
if ( this._viewarea._visDbgBuf === undefined )
{
this._viewarea._visDbgBuf = ( this._x3dElem.getAttribute( "showLog" ) === "true" );
}
this._viewarea._visDbgBuf = !this._viewarea._visDbgBuf;
x3dom.debug.logContainer.style.display = ( this._viewarea._visDbgBuf == true ) ? "block" : "none";
break;
case 101: /* e, examine mode */
nav.setType( "examine", this._viewarea );
break;
case 102: /* f, fly mode */
nav.setType( "fly", this._viewarea );
break;
case 103: /* g, game mode */
nav.setType( "game", this._viewarea );
break;
case 104: /* h, helicopter mode */
nav.setType( "helicopter", this._viewarea );
break;
case 105: /* i, fit all */
this._viewarea.fit( this._scene._lastMin, this._scene._lastMax );
break;
case 108: /* l, lookAt mode */
nav.setType( "lookat", this._viewarea );
break;
case 109: /* m, toggle "points" attribute */
//"0" = triangles
//"1" = points
//"2" = lines
//TODO: here, as option "2", we originally rendered triangle meshes as lines
// instead, we should create a separate line buffer and render it
this._viewarea._points = ++this._viewarea._points % 2;
break;
case 110: /* n, turntable */
nav.setType( "turntable", this._viewarea );
break;
case 111: /* o, look around like in fly, but don't move */
nav.setType( "lookaround", this._viewarea );
break;
case 112: /* p, switch intersect type */
switch ( this._scene._vf.pickMode.toLowerCase() )
{
case "idbuf":
this._scene._vf.pickMode = "color";
break;
case "color":
this._scene._vf.pickMode = "texCoord";
break;
case "texcoord":
this._scene._vf.pickMode = "box";
break;
default:
this._scene._vf.pickMode = "idBuf";
break;
}
x3dom.debug.logInfo( "Switch pickMode to '" + this._scene._vf.pickMode + "'." );
break;
case 114: /* r, reset view */
this._viewarea.resetView();
break;
case 115: /* s, toggle small feature culling */
env._vf.smallFeatureCulling = !env._vf.smallFeatureCulling;
x3dom.debug.logInfo( "Small feature culling " + ( env._vf.smallFeatureCulling ? "on" : "off" ) );
break;
case 116: /* t, light view */
if ( this._nodeBag.lights.length > 0 )
{
this._viewarea.animateTo( this._viewarea.getLightMatrix()[ 0 ], this._scene.getViewpoint() );
}
break;
case 117: /* u, upright position */
this._viewarea.uprightView();
break;
case 118: /* v, print viewpoint position/orientation */
var that = this;
( function ()
{
var viewpoint = that._viewarea._scene.getViewpoint();
var mat_view = that._viewarea.getViewMatrix().inverse();
var rotation = new x3dom.fields.Quaternion( 0, 0, 1, 0 );
rotation.setValue( mat_view );
var rot = rotation.toAxisAngle();
var translation = mat_view.e3();
var center = viewpoint.getCenterOfRotation();
x3dom.debug.logInfo( "\n&lt;Viewpoint position=\"" + translation.x.toFixed( 5 ) + " "
+ translation.y.toFixed( 5 ) + " " + translation.z.toFixed( 5 ) + "\" " +
"orientation=\"" + rot[ 0 ].x.toFixed( 5 ) + " " + rot[ 0 ].y.toFixed( 5 ) + " "
+ rot[ 0 ].z.toFixed( 5 ) + " " + rot[ 1 ].toFixed( 5 ) + "\" \n\t" +
"zNear=\"" + viewpoint.getNear().toFixed( 5 ) + "\" " +
"zFar=\"" + viewpoint.getFar().toFixed( 5 ) + "\" " +
"centerOfRotation=\"" + center.x.toFixed( 5 ) + " " + center.y.toFixed( 5 ) + " " + center.z.toFixed( 5 ) + "\" " +
"fieldOfView=\"" + viewpoint.getFieldOfView().toFixed( 5 ) + "\" " +
"description=\"" + viewpoint._vf.description + "\"&gt;" +
"&lt;/Viewpoint&gt;" );
} )();
break;
case 119: /* w, walk mode */
nav.setType( "walk", this._viewarea );
break;
case 121: /* y, freefly mode */
nav.setType( "freefly", this._viewarea );
break;
default:
}
};
x3dom.X3DDocument.prototype.shutdown = function ( ctx )
{
if ( !ctx )
{
return;
}
ctx.shutdown( this._viewarea );
};
x3dom.X3DDocument.prototype.hasAnimationStateChanged = function ()
{
if ( !this._viewarea )
{
return false;
}
return this._viewarea.hasAnimationStateChanged();
};
x3dom.X3DDocument.prototype.isAnimating = function ()
{
if ( !this._viewarea )
{
return false;
}
return this._viewarea.isAnimating();
};
x3dom.X3DDocument.prototype.incrementDownloads = function ()
{
this.downloadCount++;
};
x3dom.X3DDocument.prototype.decrementDownloads = function ()
{
this.downloadCount--;
};
x3dom.X3DDocument.prototype.cleanNodeBag = function ( bag, node )
{
for ( var i = 0, n = bag.length; i < n; i++ )
{
if ( bag[ i ] === node )
{
bag.splice( i, 1 );
break;
}
}
};
x3dom.X3DDocument.prototype.removeX3DOMBackendGraph = function ( domNode )
{
var children = domNode.childNodes;
for ( var i = 0, n = children.length; i < n; i++ )
{
this.removeX3DOMBackendGraph( children[ i ] );
}
if ( domNode._x3domNode )
{
var node = domNode._x3domNode;
var nameSpace = node._nameSpace;
if ( x3dom.isa( node, x3dom.nodeTypes.X3DShapeNode ) )
{
if ( node._cleanupGLObjects )
{
node._cleanupGLObjects( true );
// TODO: more cleanups, e.g. texture/shader cache?
}
if ( x3dom.nodeTypes.Shape.idMap.nodeID[ node._objectID ] )
{
delete x3dom.nodeTypes.Shape.idMap.nodeID[ node._objectID ];
}
}
else if ( x3dom.isa( node, x3dom.nodeTypes.TimeSensor ) )
{
this.cleanNodeBag( this._nodeBag.timer, node );
}
else if ( x3dom.isa( node, x3dom.nodeTypes.X3DLightNode ) )
{
this.cleanNodeBag( this._nodeBag.lights, node );
}
else if ( x3dom.isa( node, x3dom.nodeTypes.X3DFollowerNode ) )
{
this.cleanNodeBag( this._nodeBag.followers, node );
}
else if ( x3dom.isa( node, x3dom.nodeTypes.X3DTransformNode ) )
{
this.cleanNodeBag( this._nodeBag.trans, node );
}
else if ( x3dom.isa( node, x3dom.nodeTypes.RenderedTexture ) )
{
this.cleanNodeBag( this._nodeBag.renderTextures, node );
if ( node._cleanupGLObjects )
{
node._cleanupGLObjects();
}
}
else if ( x3dom.isa( node, x3dom.nodeTypes.X3DPointingDeviceSensorNode ) )
{
this.cleanNodeBag( this._nodeBag.affectedPointingSensors, node );
}
else if ( x3dom.isa( node, x3dom.nodeTypes.Texture ) )
{
node.shutdown(); // general texture might have video
}
else if ( x3dom.isa( node, x3dom.nodeTypes.AudioClip ) )
{
node.shutdown();
}
else if ( x3dom.isa( node, x3dom.nodeTypes.X3DBindableNode ) )
{
var stack = node._stack;
if ( stack )
{
node.bind( false );
this.cleanNodeBag( stack._bindBag, node );
}
// Background may have geometry
if ( node._cleanupGLObjects )
{
node._cleanupGLObjects();
}
}
else if ( x3dom.isa( node, x3dom.nodeTypes.Scene ) )
{
if ( node._webgl )
{
node._webgl = null;
// TODO; explicitly delete all gl objects
}
}
//do not remove node from namespace if it was only "USE"d
if ( nameSpace && !( domNode.getAttribute( "use" ) || domNode.getAttribute( "USE" ) ) )
{
nameSpace.removeNode( node._DEF );
//remove imported node from namespace
var superInlineNode = nameSpace.superInlineNode;
if ( superInlineNode && superInlineNode._nameSpace )
{
var imports = superInlineNode._nameSpace.imports;
var exports = nameSpace.exports;
var inlineDEFMap = imports.get( superInlineNode._DEF );
if ( inlineDEFMap )
{
exports.forEach( function ( localDEF, exportedAS )
{
if ( node._DEF == localDEF )
{
inlineDEFMap.forEach( function ( importedDEF, importedAS )
{
if ( exportedAS == importedDEF )
{
delete superInlineNode._nameSpace.defMap[ importedAS ];
}
} );
}
} );
}
}
}
node._xmlNode = null;
delete domNode._x3domNode;
}
};
x3dom.X3DDocument.prototype.getParentNode = function ( parentNode )
{
// move dom up if parent is metagroup
if ( parentNode && parentNode.localName.toLowerCase() == "x3dommetagroup" )
{
parentNode = this.getParentNode( parentNode.parentNode );
}
return parentNode;
};
x3dom.X3DDocument.prototype.onAttributeChanged = function ( target, attributeName, attributeValue )
{
if ( "_x3domNode" in target )
{
target._x3domNode.updateField( attributeName, attributeValue );
this.needRender = true;
}
};
x3dom.X3DDocument.prototype.onNodeRemoved = function ( removedNode, target )
{
var domNode = removedNode;
if ( !domNode )
{
return;
}
if ( domNode.querySelectorAll )
{
domNode.querySelectorAll( "*" ).forEach( function ( node )
{
node.highlight = null;
node.addEventListener = null;
node.removeEventListener = null;
} );
}
var parentNode = this.getParentNode( target );
if ( parentNode && "_x3domNode" in parentNode && "_x3domNode" in domNode )
{
var parent = parentNode._x3domNode;
var child = domNode._x3domNode;
var doc = child.findX3DDoc();
if ( doc )
{
var pickInfo = doc._viewarea._pickingInfo;
// quite coarse; perhaps better to check _after_ removal if these still exist
pickInfo.firstObj = null;
pickInfo.lastObj = null;
pickInfo.lastClickObj = null;
pickInfo.pickObj = null;
}
if ( parent && child )
{
parent.removeChild( child );
parent.nodeChanged();
this.removeX3DOMBackendGraph( domNode );
if ( this._viewarea && this._viewarea._scene )
{
this._viewarea._scene.nodeChanged();
this._viewarea._scene.updateVolume();
this.needRender = true;
}
}
}
else if ( domNode.localName &&
domNode.localName.toUpperCase() == "ROUTE" &&
domNode._nodeNameSpace )
{
var fromNode = domNode._nodeNameSpace.defMap[ domNode.getAttribute( "fromNode" ) ];
var toNode = domNode._nodeNameSpace.defMap[ domNode.getAttribute( "toNode" ) ];
if ( fromNode && toNode )
{
fromNode.removeRoute( domNode.getAttribute( "fromField" ),
toNode,
domNode.getAttribute( "toField" ) );
}
}
//Added by microaaron for removing IMPORT/EXPORT, 2021.11
else if ( domNode.localName &&
domNode.localName.toUpperCase() == "IMPORT" &&
domNode._nodeNameSpace )
{
var inlineDEF = domNode.getAttribute( "inlineDEF" ) || domNode.getAttribute( "inlinedef" );
var importedDEF = domNode.getAttribute( "importedDEF" ) || domNode.getAttribute( "importeddef" );
var AS = domNode.getAttribute( "AS" ) || domNode.getAttribute( "as" );
if ( !inlineDEF || !importedDEF )
{
return;
}
if ( !AS )
{
AS = importedDEF;
}
var importsMap = domNode._nodeNameSpace.imports;
var inlineDEFMap = importsMap.get( inlineDEF );
if ( inlineDEFMap )
{
if ( inlineDEFMap.get( AS ) == importedDEF )
{
delete domNode._nodeNameSpace.defMap[ AS ];
inlineDEFMap.delete( AS );
if ( inlineDEFMap.size == 0 )
{
importsMap.delete( inlineDEF );
}
}
}
}
//Added by microaaron for removing IMPORT/EXPORT, 2021.11
else if ( domNode.localName &&
domNode.localName.toUpperCase() == "EXPORT" &&
domNode._nodeNameSpace )
{
var localDEF = domNode.getAttribute( "localDEF" ) || domNode.getAttribute( "localdef" );
var AS = domNode.getAttribute( "AS" ) || domNode.getAttribute( "as" );
if ( !localDEF )
{
return;
}
if ( !AS )
{
AS = localDEF;
}
var exportsMap = domNode._nodeNameSpace.exports;
if ( exportsMap.get( AS ) == localDEF )
{
exportsMap.delete( AS );
}
}
};
x3dom.X3DDocument.prototype.onX3DNodeRemoved = function ( removedNode, target )
{
var domNodes = [];
if ( "querySelector" in removedNode && removedNode.querySelector( "X3D" ) )
{
domNodes = removedNode.querySelectorAll( "X3D" );
}
if ( removedNode.localName && removedNode.localName.toUpperCase() == "X3D" )
{
domNodes = [ removedNode ];
}
domNodes.forEach( function ( domNode )
{
var runtime = domNode.runtime;
if ( runtime && runtime.canvas && runtime.canvas.doc && runtime.canvas.doc._scene )
{
var sceneNode = runtime.canvas.doc._scene._xmlNode;
this.removeX3DOMBackendGraph( sceneNode );
// also clear corresponding X3DCanvas element
for ( var i = 0; i < x3dom.canvases.length; i++ )
{
if ( x3dom.canvases[ i ] === runtime.canvas )
{
x3dom.canvases[ i ].doc.shutdown( x3dom.canvases[ i ].gl );
x3dom.canvases.splice( i, 1 );
break;
}
}
runtime.canvas.doc._scene = null;
runtime.canvas.doc._viewarea = null;
runtime.canvas.doc = null;
runtime.canvas = null;
runtime = null;
domNode.context = null;
domNode.runtime = null;
}
}, this );
};
x3dom.X3DDocument.prototype.onNodeAdded = function ( addedNode, target )
{
var child = addedNode,
parentNode = this.getParentNode( target );
if ( ( parentNode.tagName && parentNode.tagName.toLowerCase() == "inline" ) || ! ( "_x3domNode" in parentNode ) )
{
return;
}
var parent = parentNode._x3domNode;
if ( !parent || !parent._nameSpace || !( child instanceof Element ) )
{
x3dom.debug.logWarning( "No _nameSpace in onNodeAdded" );
return;
}
if ( "_x3domNode" in child )
{
if ( child._x3domNode._parentNodes.includes( parent ) )
{
return;
}
parent.removeChild( child._x3domNode ); // may never happen
this.removeX3DOMBackendGraph( child );
}
if ( x3dom.caps.DOMNodeInsertedEvent_perSubtree )
{
this.removeX3DOMBackendGraph( child ); // not really necessary...
}
var newNode = parent._nameSpace.setupTree( child );
parent.addChild( newNode, child.getAttribute( "containerField" ) );
parent.nodeChanged();
var grandParentNode = parentNode.parentNode;
if ( grandParentNode && grandParentNode._x3domNode )
{
grandParentNode._x3domNode.nodeChanged();
}
if ( this._viewarea && this._viewarea._scene )
{
this._viewarea._scene.nodeChanged();
this._viewarea._scene.updateVolume();
this.needRender = true;
}
};
x3dom.X3DDocument.prototype.onX3DNodeAdded = function ( addedNode, target )
{
var domNode = addedNode;
if ( domNode.localName && domNode.localName.toUpperCase() == "X3D" )
{
//x3dom.reload();
}
};
x3dom.X3DDocument.prototype.onMutation = function ( records )
{
var i,
n,
record,
newValue;
var set_prefix = "set_";
for ( i = 0, n = records.length; i < n; i++ )
{
record = records[ i ];
if ( record.type === "attributes" ) // && ( record.oldValue != null ) )
{
if ( record.oldValue != null || record.attributeName.startsWith( set_prefix ) )
{
newValue = record.target.getAttribute( record.attributeName );
this.onAttributeChanged( record.target,
record.attributeName,
newValue );
}
}
else if ( record.type === "childList" )
{
if ( record.removedNodes.length )
{
for ( var j = 0, k = record.removedNodes.length; j < k; j++ )
{
this.onNodeRemoved( record.removedNodes[ j ], record.target );
}
}
if ( record.addedNodes.length )
{
for ( var j = 0, k = record.addedNodes.length; j < k; j++ )
{
this.onNodeAdded( record.addedNodes[ j ], record.target );
}
}
}
}
};
x3dom.X3DDocument.prototype.onX3DMutation = function ( records )
{
for ( var i = 0, n = records.length; i < n; i++ )
{
if ( records[ i ].type === "childList" )
{
if ( records[ i ].removedNodes.length )
{
for ( var j = 0, k = records[ i ].removedNodes.length; j < k; j++ )
{
this.onX3DNodeRemoved( records[ i ].removedNodes[ j ], records[ i ].target );
}
}
//if ( records[ i ].addedNodes.length )
//{
// for ( var j = 0, k = records[ i ].addedNodes.length; j < k; j++ )
// {
// this.onX3DNodeAdded( records[ i ].addedNodes[ j ], records[ i ].target );
// }
//}
}
}
};
/**
* X3DOM JavaScript Library
* http://www.x3dom.org
*
* (C)2009 Fraunhofer IGD, Darmstadt, Germany
* Dual licensed under the MIT and GPL
*
* Based on code originally provided by
* Philip Taylor: http://philip.html5.org
*/
/**
* MatrixMixer Constructor
*
* @param beginTime
* @param endTime
* @constructor
*/
x3dom.MatrixMixer = function ( beginTime, endTime )
{
this.beginTime = beginTime || 0;
this.endTime = endTime || 1;
this.isMixing = false;
this._beginMat = x3dom.fields.SFMatrix4f.identity();
this._beginInvMat = x3dom.fields.SFMatrix4f.identity();
this._beginLogMat = x3dom.fields.SFMatrix4f.identity();
this._endMat = x3dom.fields.SFMatrix4f.identity();
this._endLogMat = x3dom.fields.SFMatrix4f.identity();
this._beginRot = new x3dom.fields.Quaternion();
this._endRot = new x3dom.fields.Quaternion();
this._beginPos = new x3dom.fields.SFVec3f();
this._endPos = new x3dom.fields.SFVec3f();
this._result = x3dom.fields.SFMatrix4f.identity();
this._useQuaternion = false;
this._isVPtarget = false;
};
/**
* MatrixMixer Calculate Fraction
*
* @param time
* @returns {number}
* @private
*/
x3dom.MatrixMixer.prototype._calcFraction = function ( time )
{
var fraction = ( time - this.beginTime ) / ( this.endTime - this.beginTime );
return ( Math.sin( ( fraction * Math.PI ) - ( Math.PI / 2 ) ) + 1 ) / 2.0;
};
/**
* MatrixMixer Is Valid?
*
* @returns {boolean}
* @private
*/
x3dom.MatrixMixer.prototype._isValid = function ()
{
var angles = this._beginMat.inverse().mult( this._endMat ).getEulerAngles();
return ( Math.abs( angles[ 0 ] ) != Math.PI &&
Math.abs( angles[ 1 ] ) != Math.PI &&
Math.abs( angles[ 2 ] ) != Math.PI );
};
/**
* MatrixMixer Prepare Quaternion Animation
*
* @private
*/
x3dom.MatrixMixer.prototype._prepareQuaternionAnimation = function ()
{
this._beginRot.setValue( this._beginMat );
this._endRot.setValue( this._endMat );
this._beginPos = this._beginMat.e3();
this._endPos = this._endMat.e3();
this._useQuaternion = true;
};
/**
* MatrixMixer Reset
*
* @private
*/
x3dom.MatrixMixer.prototype.reset = function ()
{
this.beginTime = 0;
this.endTime = 0;
this._useQuaternion = false;
this.isMixing = false;
};
/**
* MatrixMixer Is Active?
*
* @returns {boolean}
*/
x3dom.MatrixMixer.prototype.isActive = function ()
{
return ( this.beginTime > 0 );
};
/**
* MatrixMixer Set Begin Matrix
*
* @param mat
*/
x3dom.MatrixMixer.prototype.setBeginMatrix = function ( mat )
{
this._beginMat.setValues( mat );
this._beginInvMat = mat.inverse();
this._beginLogMat = x3dom.fields.SFMatrix4f.zeroMatrix();
};
/**
* MatrixMixer get End Matrix
*
* @return mat
*/
x3dom.MatrixMixer.prototype.getBeginMatrix = function ( mat )
{
return this._beginMat;
};
/**
* MatrixMixer Set End Matrix
*
* @param mat
*/
x3dom.MatrixMixer.prototype.setEndMatrix = function ( mat )
{
this._endMat.setValues( mat );
if ( !this._isValid() )
{
this._prepareQuaternionAnimation();
}
this._endLogMat = this._endMat.mult( this._beginInvMat ).log();
this._logDiffMat = this._endLogMat.addScaled( this._beginLogMat, -1 );
};
/**
* MatrixMixer get End Matrix
*
* @return mat
*/
x3dom.MatrixMixer.prototype.getEndMatrix = function ( mat )
{
return this._endMat;
};
/**
* MatrixMixer Mix Quaternion
*
* @param fraction
* @returns {*}
* @private
*/
x3dom.MatrixMixer.prototype._mixQuaternion = function ( fraction )
{
var rotation = this._beginRot.slerp( this._endRot, fraction );
var translation = this._beginPos.addScaled( this._endPos.subtract( this._beginPos ), fraction );
this._result.setRotate( rotation );
this._result.setTranslate( translation );
return this._result.copy();
};
/**
* MatrixMixer Mix Matrix
*
* @param fraction
* @returns {x3dom.fields.SFMatrix4f|*|void}
* @private
*/
x3dom.MatrixMixer.prototype._mixMatrix = function ( fraction )
{
return this._logDiffMat.multiply( fraction ).add( this._beginLogMat ).exp().mult( this._beginMat );
};
/**
* MatrixMixer Mix
*
* @param time
* @returns {*}
*/
x3dom.MatrixMixer.prototype.mix = function ( time )
{
if ( time <= this.beginTime )
{
return this._beginMat;
}
else if ( time >= this.endTime )
{
this.reset();
return this._endMat;
}
else
{
this.isMixing = true;
var fraction = this._calcFraction( time );
if ( this._useQuaternion )
{
return this._mixQuaternion( fraction );
}
else
{
return this._mixMatrix( fraction );
}
}
};
/**
* X3DOM JavaScript Library
* http://www.x3dom.org
*
* (C)2009 Fraunhofer IGD, Darmstadt, Germany
* Dual licensed under the MIT and GPL
*
* Based on code originally provided by
* Philip Taylor: http://philip.html5.org
*/
/**
* @class x3dom.Mesh
*/
x3dom.Mesh = function ( parent )
{
this._parent = parent;
this._vol = new x3dom.fields.BoxVolume();
this._invalidate = true;
this._numFaces = 0;
this._numCoords = 0;
// cp. x3dom.Utils.primTypeDic for type list
this._primType = "TRIANGLES";
this._positions = [];
this._normals = [];
this._texCoords = [];
this._texCoords2 = [];
this._colors = [];
this._indices = [];
this._tangents = [];
this._binormals = [];
this._positions[ 0 ] = [];
this._normals[ 0 ] = [];
this._texCoords[ 0 ] = [];
this._texCoords2[ 0 ] = [];
this._colors[ 0 ] = [];
this._indices[ 0 ] = [];
this._tangents[ 0 ] = [];
this._binormals[ 0 ] = [];
};
x3dom.Mesh.prototype._dynamicFields = {}; // can hold X3DVertexAttributeNodes
/*
x3dom.Mesh.prototype._positions = [];
x3dom.Mesh.prototype._normals = [];
x3dom.Mesh.prototype._texCoords = [];
x3dom.Mesh.prototype._colors = [];
x3dom.Mesh.prototype._indices = [];
*/
x3dom.Mesh.prototype._numPosComponents = 3;
x3dom.Mesh.prototype._numTexComponents = 2;
x3dom.Mesh.prototype._numTex2Components = 2;
x3dom.Mesh.prototype._numColComponents = 3;
x3dom.Mesh.prototype._numNormComponents = 3;
x3dom.Mesh.prototype._numTangentComponents = 3;
x3dom.Mesh.prototype._numBinormalComponents = 3;
x3dom.Mesh.prototype._lit = true;
x3dom.Mesh.prototype._vol = null;
x3dom.Mesh.prototype._invalidate = true;
x3dom.Mesh.prototype._numFaces = 0;
x3dom.Mesh.prototype._numCoords = 0;
/**
* Get Volume
*
* @returns {null}
*/
x3dom.Mesh.prototype.getVolume = function ()
{
if ( this._invalidate == true && !this._vol.isValid() )
{
var coords = this._positions[ 0 ];
var n = coords.length;
if ( n >= 3 )
{
var initVal = new x3dom.fields.SFVec3f( coords[ 0 ], coords[ 1 ], coords[ 2 ] );
this._vol.setBounds( initVal, initVal );
for ( var i = 3; i < n; i += 3 )
{
if ( this._vol.min.x > coords[ i ] ) { this._vol.min.x = coords[ i ]; }
if ( this._vol.min.y > coords[ i + 1 ] ) { this._vol.min.y = coords[ i + 1 ]; }
if ( this._vol.min.z > coords[ i + 2 ] ) { this._vol.min.z = coords[ i + 2 ]; }
if ( this._vol.max.x < coords[ i ] ) { this._vol.max.x = coords[ i ]; }
if ( this._vol.max.y < coords[ i + 1 ] ) { this._vol.max.y = coords[ i + 1 ]; }
if ( this._vol.max.z < coords[ i + 2 ] ) { this._vol.max.z = coords[ i + 2 ]; }
}
this._invalidate = false;
}
}
return this._vol;
};
/**
* Invalidate
*
*/
x3dom.Mesh.prototype.invalidate = function ()
{
this._invalidate = true;
this._vol.invalidate();
};
/**
* Is Valid?
*
* @returns {*|boolean}
*/
x3dom.Mesh.prototype.isValid = function ()
{
return this._vol.isValid();
};
/**
* Get Center
*
* @returns {*}
*/
x3dom.Mesh.prototype.getCenter = function ()
{
return this.getVolume().getCenter();
};
/**
* Get Diameter
*
* @returns {*}
*/
x3dom.Mesh.prototype.getDiameter = function ()
{
return this.getVolume().getDiameter();
};
/**
* Do Intersect
*
* @param line
* @returns {*}
*/
x3dom.Mesh.prototype.doIntersect = function ( line )
{
var vol = this.getVolume();
var isect = line.intersect( vol.min, vol.max );
//TODO: iterate over all faces!
if ( isect && line.enter < line.dist )
{
//x3dom.debug.logInfo("Hit \"" + this._parent._xmlNode.localName + "/ " +
// this._parent._DEF + "\" at dist=" + line.enter.toFixed(4));
line.dist = line.enter;
line.hitObject = this._parent;
line.hitPoint = line.pos.add( line.dir.multiply( line.enter ) );
}
return isect;
};
/**
* Calculate Normals
*
* @param creaseAngle
* @param ccw
*/
x3dom.Mesh.prototype.calcNormals = function ( creaseAngle, ccw )
{
if ( ccw === undefined )
{ccw = true;}
var multInd = this._multiIndIndices && this._multiIndIndices.length;
var idxs = multInd ? this._multiIndIndices : this._indices[ 0 ];
var coords = this._positions[ 0 ];
var vertNormals = [];
var vertFaceNormals = [];
var i,
j,
m = coords.length,
a,
b,
n = null;
var num = ( this._posSize !== undefined && this._posSize > m ) ? this._posSize / 3 : m / 3;
num = 3 * ( ( num - Math.floor( num ) > 0 ) ? Math.floor( num + 1 ) : num );
for ( i = 0; i < num; ++i )
{
vertFaceNormals[ i ] = [];
}
num = idxs.length;
for ( i = 0; i < num; i += 3 )
{
var ind_i0,
ind_i1,
ind_i2,
t;
if ( !multInd )
{
ind_i0 = idxs[ i ] * 3;
ind_i1 = idxs[ i + 1 ] * 3;
ind_i2 = idxs[ i + 2 ] * 3;
t = new x3dom.fields.SFVec3f( coords[ ind_i1 ], coords[ ind_i1 + 1 ], coords[ ind_i1 + 2 ] );
a = new x3dom.fields.SFVec3f( coords[ ind_i0 ], coords[ ind_i0 + 1 ], coords[ ind_i0 + 2 ] ).subtract( t );
b = t.subtract( new x3dom.fields.SFVec3f( coords[ ind_i2 ], coords[ ind_i2 + 1 ], coords[ ind_i2 + 2 ] ) );
// this is needed a few lines below
ind_i0 = i * 3;
ind_i1 = ( i + 1 ) * 3;
ind_i2 = ( i + 2 ) * 3;
}
else
{
ind_i0 = i * 3;
ind_i1 = ( i + 1 ) * 3;
ind_i2 = ( i + 2 ) * 3;
t = new x3dom.fields.SFVec3f( coords[ ind_i1 ], coords[ ind_i1 + 1 ], coords[ ind_i1 + 2 ] );
a = new x3dom.fields.SFVec3f( coords[ ind_i0 ], coords[ ind_i0 + 1 ], coords[ ind_i0 + 2 ] ).subtract( t );
b = t.subtract( new x3dom.fields.SFVec3f( coords[ ind_i2 ], coords[ ind_i2 + 1 ], coords[ ind_i2 + 2 ] ) );
}
n = a.cross( b ).normalize();
if ( !ccw )
{
n = n.negate();
}
if ( creaseAngle <= x3dom.fields.Eps )
{
vertNormals[ ind_i0 ] = vertNormals[ ind_i1 ] = vertNormals[ ind_i2 ] = n.x;
vertNormals[ ind_i0 + 1 ] = vertNormals[ ind_i1 + 1 ] = vertNormals[ ind_i2 + 1 ] = n.y;
vertNormals[ ind_i0 + 2 ] = vertNormals[ ind_i1 + 2 ] = vertNormals[ ind_i2 + 2 ] = n.z;
}
else
{
vertFaceNormals[ idxs[ i ] ].push( n );
vertFaceNormals[ idxs[ i + 1 ] ].push( n );
vertFaceNormals[ idxs[ i + 2 ] ].push( n );
}
}
// TODO: allow generic creaseAngle
if ( creaseAngle > x3dom.fields.Eps )
{
for ( i = 0; i < m; i += 3 )
{
var iThird = i / 3,
arr;
if ( !multInd )
{
arr = vertFaceNormals[ iThird ];
}
else
{
arr = vertFaceNormals[ idxs[ iThird ] ];
}
num = arr.length;
n = new x3dom.fields.SFVec3f( 0, 0, 0 );
for ( j = 0; j < num; ++j )
{
n = n.add( arr[ j ] );
}
n = n.normalize();
vertNormals[ i ] = n.x;
vertNormals[ i + 1 ] = n.y;
vertNormals[ i + 2 ] = n.z;
}
}
this._normals[ 0 ] = vertNormals;
};
/**
* Split Mesh
*
* @param primStride Number of index entries per primitive, for example 3 for TRIANGLES
*/
x3dom.Mesh.prototype.splitMesh = function ( primStride, checkMultiIndIndices )
{
var pStride,
isMultiInd;
if ( primStride === undefined )
{
pStride = 3;
}
else
{
pStride = primStride;
}
if ( checkMultiIndIndices === undefined )
{
checkMultiIndIndices = false;
}
var MAX = x3dom.Utils.maxIndexableCoords;
//adapt MAX to match the primitive stride
MAX = Math.floor( MAX / pStride ) * pStride;
if ( this._positions[ 0 ].length / 3 <= MAX && !checkMultiIndIndices )
{
return;
}
if ( checkMultiIndIndices )
{
isMultiInd = this._multiIndIndices && this._multiIndIndices.length;
}
else
{
isMultiInd = false;
}
var positions = this._positions[ 0 ];
var normals = this._normals[ 0 ];
var texCoords = this._texCoords[ 0 ];
var colors = this._colors[ 0 ];
var indices = isMultiInd ? this._multiIndIndices : this._indices[ 0 ];
var tangents = this._tangents[ 0 ];
var binormals = this._binormals[ 0 ];
var i = 0;
do
{
this._positions[ i ] = [];
this._normals[ i ] = [];
this._texCoords[ i ] = [];
this._colors[ i ] = [];
this._indices[ i ] = [];
this._tangents[ i ] = [];
this._binormals[ i ] = [];
var k = ( indices.length - ( ( i + 1 ) * MAX ) >= 0 );
if ( k )
{
this._indices[ i ] = indices.slice( i * MAX, ( i + 1 ) * MAX );
}
else
{
this._indices[ i ] = indices.slice( i * MAX );
}
if ( !isMultiInd )
{
if ( i )
{
var m = i * MAX;
for ( var j = 0, l = this._indices[ i ].length; j < l; j++ )
{
this._indices[ i ][ j ] -= m;
}
}
}
else
{
for ( var j = 0, l = this._indices[ i ].length; j < l; j++ )
{
this._indices[ i ][ j ] = j;
}
}
if ( k )
{
this._positions[ i ] = positions.slice( i * MAX * 3, 3 * ( i + 1 ) * MAX );
}
else
{
this._positions[ i ] = positions.slice( i * MAX * 3 );
}
if ( normals.length )
{
if ( k )
{
this._normals[ i ] = normals.slice( i * MAX * 3, 3 * ( i + 1 ) * MAX );
}
else
{
this._normals[ i ] = normals.slice( i * MAX * 3 );
}
}
if ( texCoords.length )
{
if ( k )
{
this._texCoords[ i ] = texCoords.slice( i * MAX * this._numTexComponents,
this._numTexComponents * ( i + 1 ) * MAX );
}
else
{
this._texCoords[ i ] = texCoords.slice( i * MAX * this._numTexComponents );
}
}
if ( colors.length )
{
if ( k )
{
this._colors[ i ] = colors.slice( i * MAX * this._numColComponents,
this._numColComponents * ( i + 1 ) * MAX );
}
else
{
this._colors[ i ] = colors.slice( i * MAX * this._numColComponents );
}
}
if ( tangents.length )
{
if ( k )
{
this._tangents[ i ] = tangents.slice( i * MAX * 3, 3 * ( i + 1 ) * MAX );
}
else
{
this._tangents[ i ] = tangents.slice( i * MAX * 3 );
}
}
if ( binormals.length )
{
if ( k )
{
this._binormals[ i ] = binormals.slice( i * MAX * 3, 3 * ( i + 1 ) * MAX );
}
else
{
this._binormals[ i ] = binormals.slice( i * MAX * 3 );
}
}
} while ( positions.length > ++i * MAX * 3 );
};
x3dom.Mesh.prototype.calcTexCoords = function ( mode )
{
this._texCoords[ 0 ] = [];
// TODO: impl. all modes that aren't handled in shader!
// FIXME: WebKit requires valid texCoords for texturing
if ( mode.toLowerCase() === "sphere-local" )
{
for ( var i = 0, j = 0, n = this._normals[ 0 ].length; i < n; i += 3 )
{
this._texCoords[ 0 ][ j++ ] = 0.5 + this._normals[ 0 ][ i ] / 2.0;
this._texCoords[ 0 ][ j++ ] = 0.5 + this._normals[ 0 ][ i + 1 ] / 2.0;
}
}
if ( mode.toLowerCase() === "coord" )
{
for ( var k = 0, l = 0, m = this._positions[ 0 ].length; k < m; k += 3 )
{
this._texCoords[ 0 ][ l++ ] = this._positions[ 0 ][ k ];
this._texCoords[ 0 ][ l++ ] = this._positions[ 0 ][ k + 1 ];
}
}
else
{
// "plane" is x3d default mapping
var min = new x3dom.fields.SFVec3f( 0, 0, 0 ),
max = new x3dom.fields.SFVec3f( 0, 0, 0 );
var vol = this.getVolume();
vol.getBounds( min, max );
var dia = max.subtract( min );
var S = 0,
T = 1;
if ( dia.x >= dia.y )
{
if ( dia.x >= dia.z )
{
S = 0;
T = dia.y >= dia.z ? 1 : 2;
}
else
{
// dia.x < dia.z
S = 2;
T = 0;
}
}
else
{
// dia.x < dia.y
if ( dia.y >= dia.z )
{
S = 1;
T = dia.x >= dia.z ? 0 : 2;
}
else
{
// dia.y < dia.z
S = 2;
T = 1;
}
}
var sDenom = 1,
tDenom = 1;
var sMin = 0,
tMin = 0;
switch ( S )
{
case 0: sDenom = dia.x; sMin = min.x; break;
case 1: sDenom = dia.y; sMin = min.y; break;
case 2: sDenom = dia.z; sMin = min.z; break;
}
switch ( T )
{
case 0: tDenom = dia.x; tMin = min.x; break;
case 1: tDenom = dia.y; tMin = min.y; break;
case 2: tDenom = dia.z; tMin = min.z; break;
}
for ( var k = 0, l = 0, m = this._positions[ 0 ].length; k < m; k += 3 )
{
this._texCoords[ 0 ][ l++ ] = ( this._positions[ 0 ][ k + S ] - sMin ) / sDenom;
this._texCoords[ 0 ][ l++ ] = ( this._positions[ 0 ][ k + T ] - tMin ) / sDenom;
}
}
};
/**
* X3DOM JavaScript Library
* http://www.x3dom.org
*
* (C)2009 Fraunhofer IGD, Darmstadt, Germany
* Dual licensed under the MIT and GPL
*
* Based on code originally provided by
* Philip Taylor: http://philip.html5.org
*/
/* This module adds documentation related functionality to the library. */
/**
* The x3dom.docs namespace.
* @namespace x3dom.docs
*/
x3dom.docs = {};
x3dom.docs.specURLMap = {
CADGeometry : "CADGeometry.html", // 32 CAD geometry component
Core : "core.html", // 7 Core component
DIS : "dis.html", // 28 Distributed interactive simulation (DIS) component
CubeMapTexturing : "env_texture.html", // 34 Cube map environmental texturing component
EnvironmentalEffects : "enveffects.html", // 24 Environmental effects component
EnvironmentalSensor : "envsensor.html", // 22 Environmental sensor component
Followers : "followers.html", // 39 Followers component
Geospatial : "geodata.html", // 25 Geospatial component
Geometry2D : "geometry2D.html", // 14 Geometry2D component
Geometry3D : "geometry3D.html", // 13 Geometry3D component
Grouping : "group.html", // 10 Grouping component
"H-Anim" : "hanim.html", // 26 Humanoid Animation (H-Anim) component
Interpolation : "interp.html", // 19 Interpolation component
KeyDeviceSensor : "keyboard.html", // 21 Key device sensor component
Layering : "layering.html", // 35 Layering component
Layout : "layout.html", // 36 Layout component
Lighting : "lighting.html", // 17 Lighting component
Navigation : "navigation.html", // 23 Navigation component
Networking : "networking.html", // 9 Networking component
NURBS : "nurbs.html", // 27 NURBS component
ParticleSystems : "particle_systems.html", // 40 Particle systems component
Picking : "picking.html", // 38 Picking component
PointingDeviceSensor : "pointingsensor.html", // 20 Pointing device sensor component
Rendering : "rendering.html", // 11 Rendering component
RigidBodyPhysics : "rigid_physics.html", // 37 Rigid body physics
Scripting : "scripting.html", // 29 Scripting component
Shaders : "shaders.html", // 31 Programmable shaders component
Shape : "shape.html", // 12 Shape component
Sound : "sound.html", // 16 Sound component
Text : "text.html", // 15 Text component
Texturing3D : "texture3D.html", // 33 Texturing3D Component
Texturing : "texturing.html", // 18 Texturing component
Time : "time.html", // 8 Time component
EventUtilities : "utils.html", // 30 Event Utilities component
VolumeRendering : "volume.html" // 41 Volume rendering component
};
x3dom.docs.specBaseURL = "https://www.web3d.org/documents/specifications/19775-1/V3.3/Part01/components/";
/**
* The dump-nodetype tree functionality in a function
*
* @returns {string} HTML Node Type List.
*/
x3dom.docs.getNodeTreeInfo = function ()
{
// Create the nodetype hierarchy
var tn,
t;
var types = "";
var objInArray = function ( array, obj )
{
for ( var i = 0; i < array.length; i++ )
{
if ( array[ i ] === obj )
{
return true;
}
}
return false;
};
var dump = function ( t, indent )
{
for ( var i = 0; i < indent; i++ )
{
types += "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
}
types += "<a href='" +
x3dom.docs.specBaseURL + x3dom.docs.specURLMap[ x3dom.nodeTypes[ t ]._compName ] + "#" + t +
"' style='color:black; text-decoration:none; font-weight:bold;'>" +
t + "</a> &nbsp; <a href='" +
x3dom.docs.specBaseURL + x3dom.docs.specURLMap[ x3dom.nodeTypes[ t ]._compName ] +
"' style='color:black; text-decoration:none; font-style:italic;'>" +
x3dom.nodeTypes[ t ]._compName + "</a><br/>";
for ( var i in x3dom.nodeTypes[ t ].childTypes[ t ] )
{
dump( x3dom.nodeTypes[ t ].childTypes[ t ][ i ], indent + 1 );
}
};
for ( tn in x3dom.nodeTypes )
{
var t = x3dom.nodeTypes[ tn ];
if ( t.childTypes === undefined )
{
t.childTypes = {};
}
while ( t.superClass )
{
if ( t.superClass.childTypes[ t.superClass._typeName ] === undefined )
{
t.superClass.childTypes[ t.superClass._typeName ] = [];
}
if ( !objInArray( t.superClass.childTypes[ t.superClass._typeName ], t._typeName ) )
{
t.superClass.childTypes[ t.superClass._typeName ].push( t._typeName );
}
t = t.superClass;
}
}
dump( "X3DNode", 0 );
return "<div class='x3dom-doc-nodes-tree'>" + types + "</div>";
};
/**
* Get Component Info
*
* @returns {string} HTML Component List.
*/
x3dom.docs.getComponentInfo = function ()
{
// Dump nodetypes by component
// but first sort alphabetically
var components = [];
var component,
result = "",
c,
cn;
for ( c in x3dom.components )
{
components.push( c );
}
components.sort();
// for (var c in x3dom.components) {
for ( cn in components )
{
c = components[ cn ];
component = x3dom.components[ c ];
result += "<h2><a href='" +
x3dom.docs.specBaseURL + x3dom.docs.specURLMap[ c ] +
"' style='color:black; text-decoration:none; font-style:italic;'>" +
c + "</a></h2>";
result += "<ul style='list-style-type:circle;'>";
// var $ul = $("#components ul:last");
for ( var t in component )
{
result += "<li><a href='" +
x3dom.docs.specBaseURL + x3dom.docs.specURLMap[ c ] + "#" + t +
"' style='color:black; text-decoration:none; font-weight:bold;'>" +
t + "</a> <ul>";
if ( ! t.startsWith( "X3D" ) )
{
try
{
var node = new x3dom.nodeTypes[ t ]();
result += "-- basic fields --";
for ( var field in node._vf )
{
result += "<li>" + field + ": " + node._vf[ field ] ;
result += "</li>";
}
}
catch ( m ) {};
try
{
result += "-- node fields --";
for ( var cfield in node._cf )
{
result += "<li>" + cfield + ": " + JSON.stringify( node._cf[ cfield ] ) ;
result += "</li>";
}
}
catch ( m ) {};
}
result += "</ul> </li>";
}
result += "</ul>";
}
return result;
};
/*
* X3DOM JavaScript Library
* http://www.x3dom.org
*
* (C)2009 Fraunhofer IGD, Darmstadt, Germany
* Dual licensed under the MIT and GPL
*
* Based on code originally provided by
* Philip Taylor: http://philip.html5.org
*/
//---------------------------------------------------------------------------------------------------------------------
x3dom.arc = {};
x3dom.arc.instance = null;
x3dom.arc.Limits = function ( min, max, initial )
{
this._min = min;
this._max = max;
this.getValue = function ( value )
{
value = this._min + ( this._max - this._min ) * value;
return this._max >= value ? ( this._min <= value ? value : this._min ) : this._max;
};
};
//---------------------------------------------------------------------------------------------------------------------
x3dom.arc.ARF = function ( name, min, max, dirFac, factorGetterFunc, factorSetterFunc, getterFunc, setterFunc )
{
this._name = name;
//start with average
this._stateValue = [ 0.5, 0.5 ];
this._limits = new x3dom.arc.Limits( min, max );
this._factorGetterFunc = factorGetterFunc;
this._factorSetterFunc = factorSetterFunc;
this._setterFunc = setterFunc;
this._getterFunc = getterFunc;
this._dirFac = dirFac;
this.getFactor = function ()
{
return this._factorGetterFunc();
};
this.update = function ( state, step )
{
var stateVal = this._stateValue[ state ] + step * this._dirFac;
this._stateValue[ state ] = 0 <= stateVal ? ( 1 >= stateVal ? stateVal : 1 ) : 0;
this._setterFunc( this._limits.getValue( this._stateValue[ state ] ) );
};
this.reset = function ()
{
this._stateValue[ 0 ] = 0.5;
this._stateValue[ 1 ] = 0.5;
};
};
//---------------------------------------------------------------------------------------------------------------------
x3dom.arc.AdaptiveRenderControl = defineClass(
null,
function ( scene )
{
x3dom.arc.instance = this;
this._scene = scene;
this._targetFrameRate = [];
this._targetFrameRate[ 0 ] = this._scene._vf.minFrameRate;
this._targetFrameRate[ 1 ] = this._scene._vf.maxFrameRate;
this._currentState = 0;
var that = this;
var environment = that._scene.getEnvironment();
this._arfs = [];
this._arfs.push(
new x3dom.arc.ARF( "smallFeatureCulling",
0, 10, -1,
function ()
{
return environment._vf.smallFeatureFactor;
},
function ( value )
{
environment._vf.smallFeatureFactor = value;
},
function ()
{
return environment._vf.smallFeatureThreshold;
},
function ( value )
{
environment._vf.smallFeatureThreshold = value;
}
)
);
this._arfs.push(
new x3dom.arc.ARF( "lowPriorityCulling",
0, 100, 1,
function ()
{
return environment._vf.lowPriorityFactor;
},
function ( value )
{
environment._vf.lowPriorityFactor = value;
},
function ()
{
return environment._vf.lowPriorityThreshold * 100;
},
function ( value )
{
environment._vf.lowPriorityThreshold = value / 100;
}
)
);
this._arfs.push(
new x3dom.arc.ARF( "tessellationDetailCulling",
1, 12, -1,
function ()
{
return environment._vf.tessellationErrorFactor;
},
function ( value )
{
environment._vf.tessellationErrorFactor = value;
},
//@todo: this factor is a static member of PopGeo... should it belong to scene instead?
function ()
{
return environment.tessellationErrorThreshold;
},
function ( value )
{
environment.tessellationErrorThreshold = value;
}
)
);
this._stepWidth = 0.1;
},
{
update : function ( state, fps ) // state: 0 = static, 1 : moving
{
this._currentState = state;
var delta = fps - this._targetFrameRate[ state ];
//to prevent flickering
this._stepWidth = Math.abs( delta ) > 10 ? 0.1 : 0.01;
/*if( (delta > 0 && state == 1) || (delta < 0 && state == 0))
return;
*/
var factorSum = 0;
var normFactors = [];
//normalize factors
var i,
n = this._arfs.length;
for ( i = 0; i < n; ++i )
{
normFactors[ i ] = this._arfs[ i ].getFactor();
if ( normFactors[ i ] > 0 )
{factorSum += normFactors[ i ];}
}
var dirFac = delta < 0 ? -1 : 1;
for ( i = 0; i < n; ++i )
{
if ( normFactors[ i ] > 0 )
{
normFactors[ i ] /= factorSum;
this._arfs[ i ].update( state, this._stepWidth * normFactors[ i ] * dirFac );
}
}
},
reset : function ()
{
for ( var i = 0, n = this._arfs.length; i < n; ++i )
{
this._arfs[ i ].reset();
}
}
}
);
//---------------------------------------------------------------------------------------------------------------------
/*
* X3DOM JavaScript Library
* http://www.x3dom.org
*
* (C)2009 Fraunhofer IGD, Darmstadt, Germany
* Dual licensed under the MIT and GPL
*
* Based on code originally provided by
* Philip Taylor: http://philip.html5.org
*/
/**
* Class: x3dom.RequestManager
*/
x3dom.RequestManager = {};
/**
*
* @type {number}
*/
x3dom.RequestManager.requests = [];
/**
*
* @type {number}
*/
x3dom.RequestManager.maxParallelRequests = 50;
/**
*
* @type {number}
*/
x3dom.RequestManager.failedRequests = 0;
/**
*
* @type {number}
*/
x3dom.RequestManager.loadedRequests = 0;
/**
*
* @type {number}
*/
x3dom.RequestManager.totalRequests = 0;
/**
*
* @type {number}
*/
x3dom.RequestManager.activeRequests = [];
/**
*
* @type {number}
*/
x3dom.RequestManager.requestHeaders = [];
/**
*
* @type {number}
*/
x3dom.RequestManager.withCredentials = false;
x3dom.RequestManager.onSendRequest = function ( counters ) {};
x3dom.RequestManager.onAbortAllRequests = function ( counters ) {};
/**
*
* @param header
* @param value
*/
x3dom.RequestManager.addRequestHeader = function ( header, value )
{
this.requestHeaders.push( { header: header, value: value } );
};
/**
*
* @private
*/
x3dom.RequestManager._sendRequest = function ()
{
this.onSendRequest( this._getCounters() );
//Check if we have reached the maximum parallel request limit
if ( this.activeRequests.length > this.maxParallelRequests )
{
return;
}
//Get next available request
var request = this.requests.pop();
//Check if the request is valid
if ( request )
{
this.activeRequests.push( request );
//Send request
request.send( null );
//Trigger next request sending
this._sendRequest();
}
};
/**
*
*/
x3dom.RequestManager._getCounters = function ()
{
return {
loaded : this.loadedRequests,
active : this.activeRequests.length,
failed : this.failedRequests,
total : this.totalRequests
};
};
/**
*
* @param request
*/
x3dom.RequestManager.addRequest = function ( request )
{
//Return if request is not a valid XMLHttpRequest
if ( !( request instanceof XMLHttpRequest ) )
{
return;
}
//Increment total request counter
this.totalRequests++;
//Set withCredentials property
request.withCredentials = this.withCredentials;
//Set available request headers
for ( var i = 0; i < this.requestHeaders.length; i++ )
{
var header = this.requestHeaders[ i ].header;
var value = this.requestHeaders[ i ].value;
request.setRequestHeader( header, value );
}
//Listen for onLoad
request.addEventListener( "load", this._onLoadHandler.bind( this ) );
//Listen for onError
request.addEventListener( "error", this._onErrorHandler.bind( this ) );
//Push it to the list
this.requests.push( request );
//Send next available request
this._sendRequest();
};
/**
*
*/
x3dom.RequestManager.abortAllRequests = function ()
{
for ( var i = 0; i < this.activeRequests.length; i++ )
{
this.activeRequests[ i ].abort();
}
this.requests = [];
this.activeRequests = [];
this.failedRequests = 0;
this.loadedRequests = 0;
this.totalRequests = 0;
this.onAbortAllRequests( this._getCounters() );
};
/**
*
*/
x3dom.RequestManager._removeActiveRequest = function ( request )
{
var idx = this.activeRequests.indexOf( request );
return this.activeRequests.splice( idx, 1 );
};
/**
*
* @param e
* @private
*/
x3dom.RequestManager._onLoadHandler = function ( e )
{
//Decrement active request counter
this._removeActiveRequest( e.target );
//Increment loaded request counter
this.loadedRequests++;
//Send next available request
this._sendRequest();
};
/**
*
* @param e
* @private
*/
x3dom.RequestManager._onErrorHandler = function ( e )
{
//Decrement active request counter
this._removeActiveRequest( e.target );
//Increment loaded request counter
this.failedRequests++;
//Send next available request
this._sendRequest();
};
/*
* X3DOM JavaScript Library
* http://www.x3dom.org
*
* (C)2009 Fraunhofer IGD, Darmstadt, Germany
* Dual licensed under the MIT and GPL
*
* Based on code originally provided by
* Philip Taylor: http://philip.html5.org
*/
x3dom.Properties = function ()
{
this.properties = {};
};
x3dom.Properties.prototype.setProperty = function ( name, value )
{
x3dom.debug.logInfo( "Properties: Setting property '" + name + "' to value '" + value + "'" );
this.properties[ name ] = value;
};
x3dom.Properties.prototype.getProperty = function ( name, def )
{
if ( this.properties[ name ] )
{
return this.properties[ name ];
}
else
{
return def;
}
};
x3dom.Properties.prototype.merge = function ( other )
{
for ( var attrname in other.properties )
{
this.properties[ attrname ] = other.properties[ attrname ];
}
};
x3dom.Properties.prototype.toString = function ()
{
var str = "";
for ( var name in this.properties )
{
str += "Name: " + name + " Value: " + this.properties[ name ] + "\n";
}
return str;
};
/*
* X3DOM JavaScript Library
* http://www.x3dom.org
*
* (C)2009 Fraunhofer IGD, Darmstadt, Germany
* Dual licensed under the MIT and GPL
*
* Based on code originally provided by
* Philip Taylor: http://philip.html5.org
*/
x3dom.DoublyLinkedList = function ()
{
this.length = 0;
this.first = null;
this.last = null;
};
x3dom.DoublyLinkedList.ListNode = function ( point, point_index, normals, colors, texCoords )
{
this.point = point;
this.point_index = point_index;
this.normals = normals;
this.colors = colors;
this.texCoords = texCoords;
this.next = null;
this.prev = null;
};
x3dom.DoublyLinkedList.prototype.appendNode = function ( node )
{
if ( this.first === null )
{
node.prev = node;
node.next = node;
this.first = node;
this.last = node;
}
else
{
node.prev = this.last;
node.next = this.first;
this.first.prev = node;
this.last.next = node;
this.last = node;
}
this.length++;
};
x3dom.DoublyLinkedList.prototype.insertAfterNode = function ( node, newNode )
{
newNode.prev = node;
newNode.next = node.next;
node.next.prev = newNode;
node.next = newNode;
if ( newNode.prev == this.last )
{
this.last = newNode;
}
this.length++;
};
x3dom.DoublyLinkedList.prototype.deleteNode = function ( node )
{
if ( this.length > 1 )
{
node.prev.next = node.next;
node.next.prev = node.prev;
if ( node == this.first )
{
this.first = node.next;
}
if ( node == this.last )
{
this.last = node.prev;
}
}
else
{
this.first = null;
this.last = null;
}
node.prev = null;
node.next = null;
this.length--;
};
x3dom.DoublyLinkedList.prototype.getNode = function ( index )
{
var node = null;
if ( index > this.length )
{
return node;
}
for ( var i = 0; i < this.length; i++ )
{
if ( i == 0 )
{
node = this.first;
}
else
{
node = node.next;
}
if ( i == index )
{
return node;
}
}
return null;
};
x3dom.DoublyLinkedList.prototype.invert = function ()
{
var tmp = null;
var node = this.first;
for ( var i = 0; i < this.length; i++ )
{
tmp = node.prev;
node.prev = node.next;
node.next = tmp;
node = node.prev;
}
tmp = this.first;
this.first = this.last;
this.last = tmp;
};
/*
* X3DOM JavaScript Library
* http://www.x3dom.org
*
* (C)2009 Fraunhofer IGD, Darmstadt, Germany
* Dual licensed under the MIT and GPL
*
* Based on code originally provided by
* Philip Taylor: http://philip.html5.org
*/
x3dom.EarClipping = {
getIndexes : function ( linklist )
{
var node = linklist.first.next;
var plane = this.identifyPlane( node.prev.point, node.point, node.next.point );
var i,
points,
point_indexes,
x,
y;
points = [];
point_indexes = [];
for ( i = 0; i < linklist.length; i++ )
{
node = linklist.getNode( i );
switch ( plane )
{
case "XY": { x = node.point.x; y = node.point.y; break; }
case "XZ": { x = node.point.z; y = node.point.x; break; }
default: { x = node.point.y; y = node.point.z; }
}
points.push( y );
points.push( x );
point_indexes.push( node.point_index );
}
var triangles = x3dom.EarCut.triangulate( points, null, 2 );
triangles = triangles.map( function ( m ) {return point_indexes[ m ];} ) ;
return triangles;
},
getMultiIndexes : function ( linklist )
{
var node = linklist.first.next;
var plane = this.identifyPlane( node.prev.point, node.point, node.next.point );
var data = {};
data.indices = [];
data.point = [];
data.normals = [];
data.colors = [];
data.texCoords = [];
var mapped = {};
mapped.indices = [];
mapped.point = [];
mapped.normals = [];
mapped.colors = [];
mapped.texCoords = [];
var points = [];
var i,
x,
y;
for ( i = 0; i < linklist.length; i++ )
{
node = linklist.getNode( i );
switch ( plane )
{
case "XY": { x = node.point.x; y = node.point.y; break; }
case "XZ": { x = node.point.z; y = node.point.x; break; }
default: { x = node.point.y; y = node.point.z; }
}
points.push( y );
points.push( x );
mapped.indices.push( node.point_index );
mapped.point.push( node.point );
if ( node.normals ) {mapped.normals.push( node.normals );}
if ( node.colors ) {mapped.colors.push( node.colors );}
if ( node.texCoords ) {mapped.texCoords.push( node.texCoords );}
}
var triangles = x3dom.EarCut.triangulate( points, null, 2 );
data.indices = triangles.map( function ( m ) {return mapped.indices[ m ];} ) ;
data.point = triangles.map( function ( m ) {return mapped.point[ m ];} ) ;
if ( node.normals ) {data.normals = triangles.map( function ( m ) {return mapped.normals[ m ];} ) ;}
if ( node.colors ) {data.colors = triangles.map( function ( m ) {return mapped.colors[ m ];} ) ;}
if ( node.texCoords ) {data.texCoords = triangles.map( function ( m ) {return mapped.texCoords[ m ];} ) ;}
return data;
},
identifyPlane : function ( p1, p2, p3 )
{
var v1x,
v1y,
v1z,
v2x,
v2y,
v2z,
v3x,
v3y,
v3z;
v1x = p2.x - p1.x; v1y = p2.y - p1.y; v1z = p2.z - p1.z;
v2x = p3.x - p1.x; v2y = p3.y - p1.y; v2z = p3.z - p1.z;
v3x = Math.abs( v1y * v2z - v1z * v2y );
v3y = Math.abs( v1z * v2x - v1x * v2z );
v3z = Math.abs( v1x * v2y - v1y * v2x );
var angle = Math.max( v3x, v3y, v3z );
if ( angle == v3x )
{
return "YZ";
}
else if ( angle == v3y )
{
return "XZ";
}
else if ( angle == v3z )
{
return "XY";
}
else
{
return "XZ"; // error
}
}
};
//TODO: adjust to directly use x3dom linked list
// move to separate file
x3dom.EarCut = {
triangulate : function mapEarcut ( data, holes, dim )
{
return earcut( data, holes, dim );
/*
The following code is
Copyright (c) 2015, Mapbox
Permission to use, copy, modify, and/or distribute this software for any purpose
with or without fee is hereby granted, provided that the above copyright notice
and this permission notice appear in all copies.
*/
function earcut ( data, holeIndices, dim )
{
dim = dim || 2;
var hasHoles = holeIndices && holeIndices.length,
outerLen = hasHoles ? holeIndices[ 0 ] * dim : data.length,
//outerNode = filterPoints(linkedList(data, 0, outerLen, dim, true, clockwise)),
//AP: remember winding order
clockwise = windingOrder( data, 0, outerLen, dim ),
outerNode = linkedList( data, 0, outerLen, dim, true, clockwise ),
triangles = [];
if ( !outerNode ) {return triangles;}
var minX,
minY,
maxX,
maxY,
x,
y,
size;
if ( hasHoles ) {outerNode = eliminateHoles( data, holeIndices, outerNode, dim );}
// if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox
if ( data.length > 80 * dim )
{
minX = maxX = data[ 0 ];
minY = maxY = data[ 1 ];
for ( var i = dim; i < outerLen; i += dim )
{
x = data[ i ];
y = data[ i + 1 ];
if ( x < minX ) {minX = x;}
if ( y < minY ) {minY = y;}
if ( x > maxX ) {maxX = x;}
if ( y > maxY ) {maxY = y;}
}
// minX, minY and size are later used to transform coords into integers for z-order calculation
size = Math.max( maxX - minX, maxY - minY );
}
earcutLinked( outerNode, triangles, dim, minX, minY, size );
//AP: preserve winding order
if ( clockwise === false ) {triangles.reverse();}
return triangles;
}
// calculate original winding order of a polygon ring
// AP: separated to get original winding order
function windingOrder ( data, start, end, dim )
{
var sum = 0;
var i,
j;
for ( i = start, j = end - dim; i < end; i += dim )
{
sum += ( data[ j ] - data[ i ] ) * ( data[ i + 1 ] + data[ j + 1 ] );
j = i;
}
//true for clockwise
return sum > 0;
}
// create a circular doubly linked list from polygon points in the specified winding order
// AP: oclockwise = original winding order
function linkedList ( data, start, end, dim, clockwise, oclockwise )
{
var i,
j,
last;
// link points into circular doubly-linked list in the specified winding order
if ( clockwise === oclockwise )
{
for ( i = start; i < end; i += dim ) {last = insertNode( i, data[ i ], data[ i + 1 ], last );}
}
else
{
for ( i = end - dim; i >= start; i -= dim ) {last = insertNode( i, data[ i ], data[ i + 1 ], last );}
}
return last;
}
// eliminate colinear or duplicate points
function filterPoints ( start, end )
{
if ( !start ) {return start;}
if ( !end ) {end = start;}
var p = start,
again;
do
{
again = false;
if ( !p.steiner && ( equals( p, p.next ) || area( p.prev, p, p.next ) === 0 ) )
{
removeNode( p );
p = end = p.prev;
if ( p === p.next ) {return null;}
again = true;
}
else
{
p = p.next;
}
} while ( again || p !== end );
return end;
}
// main ear slicing loop which triangulates a polygon (given as a linked list)
function earcutLinked ( ear, triangles, dim, minX, minY, size, pass )
{
if ( !ear ) {return;}
// interlink polygon nodes in z-order
if ( !pass && size ) {indexCurve( ear, minX, minY, size );}
var stop = ear,
prev,
next;
// iterate through ears, slicing them one by one
while ( ear.prev !== ear.next )
{
prev = ear.prev;
next = ear.next;
if ( size ? isEarHashed( ear, minX, minY, size ) : isEar( ear ) )
{
// cut off the triangle
triangles.push( prev.i / dim );
triangles.push( ear.i / dim );
triangles.push( next.i / dim );
removeNode( ear );
// skipping the next vertice leads to less sliver triangles
ear = next.next;
stop = next.next;
continue;
}
ear = next;
// if we looped through the whole remaining polygon and can't find any more ears
if ( ear === stop )
{
// try filtering points and slicing again
if ( !pass )
{
earcutLinked( filterPoints( ear ), triangles, dim, minX, minY, size, 1 );
// if this didn't work, try curing all small self-intersections locally
}
else if ( pass === 1 )
{
ear = cureLocalIntersections( ear, triangles, dim );
earcutLinked( ear, triangles, dim, minX, minY, size, 2 );
// as a last resort, try splitting the remaining polygon into two
}
else if ( pass === 2 )
{
splitEarcut( ear, triangles, dim, minX, minY, size );
}
break;
}
}
}
// check whether a polygon node forms a valid ear with adjacent nodes
function isEar ( ear )
{
var a = ear.prev,
b = ear,
c = ear.next;
if ( area( a, b, c ) >= 0 ) {return false;} // reflex, can't be an ear
// now make sure we don't have other points inside the potential ear
var p = ear.next.next;
while ( p !== ear.prev )
{
if ( pointInTriangle( a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y ) &&
area( p.prev, p, p.next ) >= 0 ) {return false;}
p = p.next;
}
return true;
}
function isEarHashed ( ear, minX, minY, size )
{
var a = ear.prev,
b = ear,
c = ear.next;
if ( area( a, b, c ) >= 0 ) {return false;} // reflex, can't be an ear
// triangle bbox; min & max are calculated like this for speed
var minTX = a.x < b.x ? ( a.x < c.x ? a.x : c.x ) : ( b.x < c.x ? b.x : c.x ),
minTY = a.y < b.y ? ( a.y < c.y ? a.y : c.y ) : ( b.y < c.y ? b.y : c.y ),
maxTX = a.x > b.x ? ( a.x > c.x ? a.x : c.x ) : ( b.x > c.x ? b.x : c.x ),
maxTY = a.y > b.y ? ( a.y > c.y ? a.y : c.y ) : ( b.y > c.y ? b.y : c.y );
// z-order range for the current triangle bbox;
var minZ = zOrder( minTX, minTY, minX, minY, size ),
maxZ = zOrder( maxTX, maxTY, minX, minY, size );
// first look for points inside the triangle in increasing z-order
var p = ear.nextZ;
while ( p && p.z <= maxZ )
{
if ( p !== ear.prev && p !== ear.next &&
pointInTriangle( a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y ) &&
area( p.prev, p, p.next ) >= 0 ) {return false;}
p = p.nextZ;
}
// then look for points in decreasing z-order
p = ear.prevZ;
while ( p && p.z >= minZ )
{
if ( p !== ear.prev && p !== ear.next &&
pointInTriangle( a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y ) &&
area( p.prev, p, p.next ) >= 0 ) {return false;}
p = p.prevZ;
}
return true;
}
// go through all polygon nodes and cure small local self-intersections
function cureLocalIntersections ( start, triangles, dim )
{
var p = start;
do
{
var a = p.prev,
b = p.next.next;
// a self-intersection where edge (v[i-1],v[i]) intersects (v[i+1],v[i+2])
if ( intersects( a, p, p.next, b ) && locallyInside( a, b ) && locallyInside( b, a ) )
{
triangles.push( a.i / dim );
triangles.push( p.i / dim );
triangles.push( b.i / dim );
// remove two nodes involved
removeNode( p );
removeNode( p.next );
p = start = b;
}
p = p.next;
} while ( p !== start );
return p;
}
// try splitting polygon into two and triangulate them independently
function splitEarcut ( start, triangles, dim, minX, minY, size )
{
// look for a valid diagonal that divides the polygon into two
var a = start;
do
{
var b = a.next.next;
while ( b !== a.prev )
{
if ( a.i !== b.i && isValidDiagonal( a, b ) )
{
// split the polygon in two by the diagonal
var c = splitPolygon( a, b );
// filter colinear points around the cuts
a = filterPoints( a, a.next );
c = filterPoints( c, c.next );
// run earcut on each half
earcutLinked( a, triangles, dim, minX, minY, size );
earcutLinked( c, triangles, dim, minX, minY, size );
return;
}
b = b.next;
}
a = a.next;
} while ( a !== start );
}
// link every hole into the outer loop, producing a single-ring polygon without holes
function eliminateHoles ( data, holeIndices, outerNode, dim )
{
var queue = [],
i,
len,
start,
end,
list;
for ( i = 0, len = holeIndices.length; i < len; i++ )
{
start = holeIndices[ i ] * dim;
end = i < len - 1 ? holeIndices[ i + 1 ] * dim : data.length;
list = linkedList( data, start, end, dim, false );
if ( list === list.next ) {list.steiner = true;}
//list = filterPoints(list);
//if (list)
queue.push( getLeftmost( list ) );
}
queue.sort( compareX );
// process holes from left to right
for ( i = 0; i < queue.length; i++ )
{
eliminateHole( queue[ i ], outerNode );
outerNode = filterPoints( outerNode, outerNode.next );
}
return outerNode;
}
function compareX ( a, b )
{
return a.x - b.x;
}
// find a bridge between vertices that connects hole with an outer ring and and link it
function eliminateHole ( hole, outerNode )
{
outerNode = findHoleBridge( hole, outerNode );
if ( outerNode )
{
var b = splitPolygon( outerNode, hole );
filterPoints( b, b.next );
}
}
// David Eberly's algorithm for finding a bridge between hole and outer polygon
function findHoleBridge ( hole, outerNode )
{
var p = outerNode,
hx = hole.x,
hy = hole.y,
qx = -Infinity,
m;
// find a segment intersected by a ray from the hole's leftmost point to the left;
// segment's endpoint with lesser x will be potential connection point
do
{
if ( hy <= p.y && hy >= p.next.y )
{
var x = p.x + ( hy - p.y ) * ( p.next.x - p.x ) / ( p.next.y - p.y );
if ( x <= hx && x > qx )
{
qx = x;
m = p.x < p.next.x ? p : p.next;
}
}
p = p.next;
} while ( p !== outerNode );
if ( !m ) {return null;}
// look for points inside the triangle of hole point, segment intersection and endpoint;
// if there are no points found, we have a valid connection;
// otherwise choose the point of the minimum angle with the ray as connection point
var stop = m,
tanMin = Infinity,
tan;
p = m.next;
while ( p !== stop )
{
if ( hx >= p.x && p.x >= m.x &&
pointInTriangle( hy < m.y ? hx : qx, hy, m.x, m.y, hy < m.y ? qx : hx, hy, p.x, p.y ) )
{
tan = Math.abs( hy - p.y ) / ( hx - p.x ); // tangential
if ( ( tan < tanMin || ( tan === tanMin && p.x > m.x ) ) && locallyInside( p, hole ) )
{
m = p;
tanMin = tan;
}
}
p = p.next;
}
return m;
}
// interlink polygon nodes in z-order
function indexCurve ( start, minX, minY, size )
{
var p = start;
do
{
if ( p.z === null ) {p.z = zOrder( p.x, p.y, minX, minY, size );}
p.prevZ = p.prev;
p.nextZ = p.next;
p = p.next;
} while ( p !== start );
p.prevZ.nextZ = null;
p.prevZ = null;
sortLinked( p );
}
// Simon Tatham's linked list merge sort algorithm
// http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html
function sortLinked ( list )
{
var i,
p,
q,
e,
tail,
numMerges,
pSize,
qSize,
inSize = 1;
do
{
p = list;
list = null;
tail = null;
numMerges = 0;
while ( p )
{
numMerges++;
q = p;
pSize = 0;
for ( i = 0; i < inSize; i++ )
{
pSize++;
q = q.nextZ;
if ( !q ) {break;}
}
qSize = inSize;
while ( pSize > 0 || ( qSize > 0 && q ) )
{
if ( pSize === 0 )
{
e = q;
q = q.nextZ;
qSize--;
}
else if ( qSize === 0 || !q )
{
e = p;
p = p.nextZ;
pSize--;
}
else if ( p.z <= q.z )
{
e = p;
p = p.nextZ;
pSize--;
}
else
{
e = q;
q = q.nextZ;
qSize--;
}
if ( tail ) {tail.nextZ = e;}
else {list = e;}
e.prevZ = tail;
tail = e;
}
p = q;
}
tail.nextZ = null;
inSize *= 2;
} while ( numMerges > 1 );
return list;
}
// z-order of a point given coords and size of the data bounding box
function zOrder ( x, y, minX, minY, size )
{
// coords are transformed into non-negative 15-bit integer range
x = 32767 * ( x - minX ) / size;
y = 32767 * ( y - minY ) / size;
x = ( x | ( x << 8 ) ) & 0x00FF00FF;
x = ( x | ( x << 4 ) ) & 0x0F0F0F0F;
x = ( x | ( x << 2 ) ) & 0x33333333;
x = ( x | ( x << 1 ) ) & 0x55555555;
y = ( y | ( y << 8 ) ) & 0x00FF00FF;
y = ( y | ( y << 4 ) ) & 0x0F0F0F0F;
y = ( y | ( y << 2 ) ) & 0x33333333;
y = ( y | ( y << 1 ) ) & 0x55555555;
return x | ( y << 1 );
}
// find the leftmost node of a polygon ring
function getLeftmost ( start )
{
var p = start,
leftmost = start;
do
{
if ( p.x < leftmost.x ) {leftmost = p;}
p = p.next;
} while ( p !== start );
return leftmost;
}
// check if a point lies within a convex triangle
function pointInTriangle ( ax, ay, bx, by, cx, cy, px, py )
{
return ( cx - px ) * ( ay - py ) - ( ax - px ) * ( cy - py ) >= 0 &&
( ax - px ) * ( by - py ) - ( bx - px ) * ( ay - py ) >= 0 &&
( bx - px ) * ( cy - py ) - ( cx - px ) * ( by - py ) >= 0;
}
// check if a diagonal between two polygon nodes is valid (lies in polygon interior)
function isValidDiagonal ( a, b )
{
return equals( a, b ) || a.next.i !== b.i && a.prev.i !== b.i && !intersectsPolygon( a, b ) &&
locallyInside( a, b ) && locallyInside( b, a ) && middleInside( a, b );
}
// signed area of a triangle
function area ( p, q, r )
{
return ( q.y - p.y ) * ( r.x - q.x ) - ( q.x - p.x ) * ( r.y - q.y );
}
// check if two points are equal
function equals ( p1, p2 )
{
return p1.x === p2.x && p1.y === p2.y;
}
// check if two segments intersect
function intersects ( p1, q1, p2, q2 )
{
return area( p1, q1, p2 ) > 0 !== area( p1, q1, q2 ) > 0 &&
area( p2, q2, p1 ) > 0 !== area( p2, q2, q1 ) > 0;
}
// check if a polygon diagonal intersects any polygon segments
function intersectsPolygon ( a, b )
{
var p = a;
do
{
if ( p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i &&
intersects( p, p.next, a, b ) ) {return true;}
p = p.next;
} while ( p !== a );
return false;
}
// check if a polygon diagonal is locally inside the polygon
function locallyInside ( a, b )
{
return area( a.prev, a, a.next ) < 0 ?
area( a, b, a.next ) >= 0 && area( a, a.prev, b ) >= 0 :
area( a, b, a.prev ) < 0 || area( a, a.next, b ) < 0;
}
// check if the middle point of a polygon diagonal is inside the polygon
function middleInside ( a, b )
{
var p = a,
inside = false,
px = ( a.x + b.x ) / 2,
py = ( a.y + b.y ) / 2;
do
{
if ( ( ( p.y > py ) !== ( p.next.y > py ) ) && ( px < ( p.next.x - p.x ) * ( py - p.y ) / ( p.next.y - p.y ) + p.x ) )
{inside = !inside;}
p = p.next;
} while ( p !== a );
return inside;
}
// link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits polygon into two;
// if one belongs to the outer ring and another to a hole, it merges it into a single ring
function splitPolygon ( a, b )
{
var a2 = new Node( a.i, a.x, a.y ),
b2 = new Node( b.i, b.x, b.y ),
an = a.next,
bp = b.prev;
a.next = b;
b.prev = a;
a2.next = an;
an.prev = a2;
b2.next = a2;
a2.prev = b2;
bp.next = b2;
b2.prev = bp;
return b2;
}
// create a node and optionally link it with previous one (in a circular doubly linked list)
function insertNode ( i, x, y, last )
{
var p = new Node( i, x, y );
if ( !last )
{
p.prev = p;
p.next = p;
}
else
{
p.next = last.next;
p.prev = last;
last.next.prev = p;
last.next = p;
}
return p;
}
function removeNode ( p )
{
p.next.prev = p.prev;
p.prev.next = p.next;
if ( p.prevZ ) {p.prevZ.nextZ = p.nextZ;}
if ( p.nextZ ) {p.nextZ.prevZ = p.prevZ;}
}
function Node ( i, x, y )
{
// vertice index in coordinates array
this.i = i;
// vertex coordinates
this.x = x;
this.y = y;
// previous and next vertice nodes in a polygon ring
this.prev = null;
this.next = null;
// z-order curve value
this.z = null;
// previous and next nodes in z-order
this.prevZ = null;
this.nextZ = null;
// indicates whether this is a steiner point
this.steiner = false;
}
}
};
/*
* X3DOM JavaScript Library
* http://www.x3dom.org
*
* (C)2009 Fraunhofer IGD, Darmstadt, Germany
* Dual licensed under the MIT and GPL
*
* Based on code originally provided by
* Philip Taylor: http://philip.html5.org
*/
x3dom.FieldInterpolator = function ( beginTime, endTime, beginValue, endValue )
{
this.beginTime = beginTime || 0;
this.endTime = endTime || 1;
this.beginValue = beginValue || 0;
this.endValue = endValue || 0;
this.isInterpolating = false;
};
x3dom.FieldInterpolator.prototype.isActive = function ()
{
return ( this.beginTime > 0 );
};
x3dom.FieldInterpolator.prototype.calcFraction = function ( time )
{
var fraction = ( time - this.beginTime ) / ( this.endTime - this.beginTime );
return ( Math.sin( ( fraction * Math.PI ) - ( Math.PI / 2 ) ) + 1 ) / 2.0;
};
x3dom.FieldInterpolator.prototype.reset = function ()
{
this.isInterpolating = false;
this.beginTime = 0;
this.endTime = 1;
this.beginValue = 0;
this.endValue = 0;
};
x3dom.FieldInterpolator.prototype.interpolate = function ( time )
{
if ( time < this.beginTime )
{
return this.beginValue;
}
else if ( time >= this.endTime )
{
var endValue = this.endValue;
this.reset();
return endValue;
}
else
{
this.isInterpolating = true;
return this.beginValue + ( this.endValue - this.beginValue ) * this.calcFraction( time );
}
};
/*
* X3DOM JavaScript Library
* http://www.x3dom.org
*
* (C)2009 Fraunhofer IGD, Darmstadt, Germany
* Dual licensed under the MIT and GPL
*
* Based on code originally provided by
* Philip Taylor: http://philip.html5.org
*/
/*****************************************************************************
* Utils class holds utility functions for renderer
*****************************************************************************/
x3dom.Utils = {};
x3dom.Utils.maxIndexableCoords = 65535;
x3dom.Utils.needLineWidth = false; // lineWidth not impl. in IE11
x3dom.Utils.measurements = [];
// http://gent.ilcore.com/2012/06/better-timer-for-javascript.html
window.performance = window.performance || {};
performance.now = ( function ()
{
return performance.now ||
performance.mozNow ||
performance.msNow ||
performance.oNow ||
performance.webkitNow ||
function ()
{
return new Date().getTime();
};
} )();
x3dom.Utils.startMeasure = function ( name )
{
var uname = name.toUpperCase();
if ( !x3dom.Utils.measurements[ uname ] )
{
x3dom.Utils.measurements[ uname ] = performance.now();
}
};
x3dom.Utils.stopMeasure = function ( name )
{
var uname = name.toUpperCase();
if ( x3dom.Utils.measurements[ uname ] )
{
var startTime = x3dom.Utils.measurements[ uname ];
delete x3dom.Utils.measurements[ uname ];
return performance.now() - startTime;
}
return 0;
};
/*****************************************************************************
*
*****************************************************************************/
x3dom.Utils.isNumber = function ( n )
{
return !isNaN( parseFloat( n ) ) && isFinite( n );
};
/*****************************************************************************
*
*****************************************************************************/
x3dom.Utils.createTexture2D = function ( gl, doc, src, bgnd, crossOrigin, scale, genMipMaps, flipY, tex )
{
flipY = flipY || false;
var texture = gl.createTexture();
//Create a black 4 pixel texture to prevent 'texture not complete' warning
var data = new Uint8Array( [ 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255 ] );
gl.bindTexture( gl.TEXTURE_2D, texture );
gl.texImage2D( gl.TEXTURE_2D, 0, gl.RGBA, 2, 2, 0, gl.RGBA, gl.UNSIGNED_BYTE, data );
if ( genMipMaps )
{
gl.generateMipmap( gl.TEXTURE_2D );
}
gl.bindTexture( gl.TEXTURE_2D, null );
texture.ready = false;
var urlIndex = 0;
if ( src == null || src == "" )
{return texture;}
var image = new Image();
switch ( crossOrigin.toLowerCase() )
{
case "anonymous": {
image.crossOrigin = "anonymous";
} break;
case "use-credentials": {
image.crossOrigin = "use-credentials";
} break;
case "none": {
//this is needed to omit the default case, if default is none, erase this and the default case
} break;
default: {
if ( x3dom.Utils.forbiddenBySOP( src ) )
{
image.crossOrigin = "anonymous";
}
}
}
var requestImageWithChannelCount = function ()
{
if ( tex && tex.getOrigChannelCount() === 0 )
{
var xhr = new XMLHttpRequest();
xhr.open( "GET", src );
xhr.onloadstart = function ()
{
xhr.responseType = "arraybuffer";
};
xhr.onload = function ()
{
var mimeType = xhr.getResponseHeader( "Content-Type" ),
imageData = new Uint8Array( xhr.response ),
channelcount = x3dom.Utils.detectChannelCount( imageData, mimeType );
if ( channelcount )
{
tex.setOrigChannelCount( channelcount );
}
image.src = x3dom.Utils.arrayBufferToObjectURL( imageData, mimeType );
};
xhr.onerror = function ()
{
x3dom.debug.logError( "[Utils|createTexture2D] Can't http request: " + src );
// always try to load, to trigger image.onerror if unsuccessful
image.src = src;
};
x3dom.RequestManager.addRequest( xhr );
}
else
{
image.src = src;
}
doc.incrementDownloads();
};
requestImageWithChannelCount();
image.onload = function ()
{
texture.originalWidth = image.width;
texture.originalHeight = image.height;
if ( scale )
{image = x3dom.Utils.scaleImage( image );}
if ( bgnd == true || flipY == true )
{
gl.pixelStorei( gl.UNPACK_FLIP_Y_WEBGL, true );
}
gl.bindTexture( gl.TEXTURE_2D, texture );
if ( tex && tex._vf.colorSpaceConversion == false )
{
gl.pixelStorei( gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, gl.NONE );
}
//gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
gl.texImage2D( gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image );
if ( genMipMaps )
{
gl.generateMipmap( gl.TEXTURE_2D );
}
gl.bindTexture( gl.TEXTURE_2D, null );
if ( bgnd == true || flipY == true )
{
gl.pixelStorei( gl.UNPACK_FLIP_Y_WEBGL, false );
}
//Save image size
texture.width = image.width;
texture.height = image.height;
texture.ready = true;
doc.decrementDownloads();
doc.needRender = true;
};
image.onerror = function ( error )
{
x3dom.Utils.tryDDSLoading( texture, gl, doc, src, genMipMaps, flipY, tex ).then( function ()
{
doc.decrementDownloads();
doc.needRender = true;
}, function ()
{
x3dom.debug.logError( "[Utils|createTexture2D] Can't load Image: " + src );
doc.decrementDownloads();
urlIndex++;
if ( tex && urlIndex < tex._vf.url.length )
{
src = tex._nameSpace.getURL( tex._vf.url[ urlIndex ] );
requestImageWithChannelCount();
}
} );
};
return texture;
};
x3dom.Utils.tryDDSLoading = function ( texture, gl, doc, src, genMipMaps, flipY, tex )
{
return x3dom.DDSLoader.load( src ).then( function ( dds )
{
if ( !dds || ( dds.isCompressed && !x3dom.caps.COMPRESSED_TEXTURE ) )
{
return;
}
gl.bindTexture( dds.type, texture );
flipY = false;
if ( flipY )
{
gl.pixelStorei( gl.UNPACK_FLIP_Y_WEBGL, true );
}
if ( !x3dom.Utils.isPowerOfTwo( dds.width ) && !x3dom.Utils.isPowerOfTwo( dds.height ) )
{
gl.texParameteri( dds.type, gl.TEXTURE_MAG_FILTER, gl.LINEAR );
gl.texParameteri( dds.type, gl.TEXTURE_MIN_FILTER, gl.LINEAR );
gl.texParameteri( dds.type, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE );
gl.texParameteri( dds.type, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE );
dds.generateMipmaps = false;
}
else if ( dds.generateMipmaps )
{
gl.texParameteri( dds.type, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR );
}
for ( var target in dds.data )
{
var width = dds.width;
var height = dds.height;
var levels = dds.data[ target ];
for ( var l = 0; l < levels.length; l++ )
{
if ( l != 0 )
{
width = Math.max( width * 0.5, 1 );
height = Math.max( height * 0.5, 1 );
}
if ( dds.format.internal < 33776 || dds.format.internal > 33779 )
{
gl.texImage2D( +target, l, dds.format.internal, width, height, 0, dds.format.format, dds.format.type, levels[ l ] );
}
else
{
gl.compressedTexImage2D( +target, l, dds.format.internal, width, height, 0, levels[ l ] );
dds.generateMipmaps = false;
}
}
}
if ( dds.generateMipmaps )
{
gl.generateMipmap( dds.type );
}
if ( flipY )
{
gl.pixelStorei( gl.UNPACK_FLIP_Y_WEBGL, false );
}
gl.bindTexture( dds.type, null );
texture.width = dds.width;
texture.height = dds.height;
texture.ready = true;
texture.textureCubeReady = true;
if ( tex && dds.channelCount )
{
tex.setOrigChannelCount( dds.channelCount );
}
} );
};
/*****************************************************************************
*
*****************************************************************************/
x3dom.Utils.createTextureCube = function ( gl, doc, src, bgnd, crossOrigin, scale, genMipMaps, flipY )
{
//Create a black 4 pixel texture to prevent 'texture not complete' warning
var data = new Uint8Array( [ 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255 ] );
var texture = gl.createTexture();
var faces;
if ( bgnd )
{
faces = [ gl.TEXTURE_CUBE_MAP_POSITIVE_Z, gl.TEXTURE_CUBE_MAP_NEGATIVE_Z,
gl.TEXTURE_CUBE_MAP_POSITIVE_Y, gl.TEXTURE_CUBE_MAP_NEGATIVE_Y,
gl.TEXTURE_CUBE_MAP_POSITIVE_X, gl.TEXTURE_CUBE_MAP_NEGATIVE_X ];
}
else
{
// back, front, bottom, top, left, right
faces = [ gl.TEXTURE_CUBE_MAP_NEGATIVE_Z, gl.TEXTURE_CUBE_MAP_POSITIVE_Z,
gl.TEXTURE_CUBE_MAP_NEGATIVE_Y, gl.TEXTURE_CUBE_MAP_POSITIVE_Y,
gl.TEXTURE_CUBE_MAP_NEGATIVE_X, gl.TEXTURE_CUBE_MAP_POSITIVE_X ];
}
texture.ready = false;
texture.pendingTextureLoads = -1;
texture.textureCubeReady = false;
var width = 0,
height = 0;
for ( var i = 0; i < faces.length; i++ )
{
gl.bindTexture( gl.TEXTURE_CUBE_MAP, texture );
gl.texImage2D( faces[ i ], 0, gl.RGBA, 2, 2, 0, gl.RGBA, gl.UNSIGNED_BYTE, data );
gl.bindTexture( gl.TEXTURE_CUBE_MAP, null );
}
if ( src.length == 1 )
{
doc.incrementDownloads();
x3dom.Utils.tryDDSLoading( texture, gl, doc, src[ 0 ], genMipMaps, flipY ).then( function ()
{
doc.decrementDownloads();
doc.needRender = true;
}, function ()
{
x3dom.debug.logError( "[Utils|createTexture2D] Can't load Image: " + src );
doc.decrementDownloads();
} );
}
else if ( src.length == 6 )
{
for ( var i = 0; i < faces.length; i++ )
{
var face = faces[ i ];
var image = new Image();
switch ( crossOrigin.toLowerCase() )
{
case "anonymous": {
image.crossOrigin = "anonymous";
} break;
case "use-credentials": {
image.crossOrigin = "use-credentials";
} break;
case "none": {
//this is needed to omit the default case, if default is none, erase this and the default case
} break;
default: {
if ( x3dom.Utils.forbiddenBySOP( src[ i ] ) )
{
image.crossOrigin = "anonymous";
}
}
}
texture.pendingTextureLoads++;
doc.incrementDownloads();
image.onload = ( function ( texture, face, image, swap )
{
return function ()
{
if ( width == 0 && height == 0 )
{
width = image.width;
height = image.height;
}
else if ( scale && ( width != image.width || height != image.height ) )
{
x3dom.debug.logWarning( "[Utils|createTextureCube] Rescaling CubeMap images, which are of different size!" );
image = x3dom.Utils.rescaleImage( image, width, height );
}
gl.pixelStorei( gl.UNPACK_FLIP_Y_WEBGL, swap );
gl.bindTexture( gl.TEXTURE_CUBE_MAP, texture );
gl.texImage2D( face, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image );
gl.bindTexture( gl.TEXTURE_CUBE_MAP, null );
gl.pixelStorei( gl.UNPACK_FLIP_Y_WEBGL, false );
texture.pendingTextureLoads--;
doc.decrementDownloads();
if ( texture.pendingTextureLoads < 0 )
{
//Save image size also for cube tex
texture.width = width;
texture.height = height;
texture.textureCubeReady = true;
if ( genMipMaps )
{
gl.bindTexture( gl.TEXTURE_CUBE_MAP, texture );
gl.generateMipmap( gl.TEXTURE_CUBE_MAP );
gl.bindTexture( gl.TEXTURE_CUBE_MAP, null );
}
x3dom.debug.logInfo( "[Utils|createTextureCube] Loading CubeMap finished..." );
doc.needRender = true;
}
};
} )( texture, face, image, bgnd );
image.onerror = function ()
{
doc.decrementDownloads();
x3dom.debug.logError( "[Utils|createTextureCube] Can't load CubeMap!" );
};
// backUrl, frontUrl, bottomUrl, topUrl, leftUrl, rightUrl (for bgnd)
image.src = src[ i ];
}
}
return texture;
};
/*****************************************************************************
*
*****************************************************************************/
x3dom.Utils.detectChannelCount = function ( data, mimeType )
{
switch ( mimeType )
{
case "image/jpeg":
return x3dom.Utils.detectChannelCountJPG( data );
case "image/png":
return x3dom.Utils.detectChannelCountPNG( data );
case "image/gif":
return x3dom.Utils.detectChannelCountGIF( data );
}
};
/*****************************************************************************
*
*****************************************************************************/
x3dom.Utils.detectChannelCountJPG = function ( data )
{
if ( data[ 0 ] != 0xff ) {return;}
if ( data[ 1 ] != 0xd8 ) {return;}
var pos = 2;
var dv = new DataView( data.buffer, data.byteOffset, data.byteLength );
while ( pos + 4 < data.byteLength )
{
if ( data[ pos ] != 0xff )
{
pos += 1;
continue;
}
var type = data[ pos + 1 ];
pos += 2;
if ( data[ pos ] == 0xff )
{
continue;
}
var length = dv.getUint16( pos );
if ( pos + length > data.byteLength )
{
return;
}
if ( length >= 7 && ( type == 0xc0 || type == 0xc2 ) )
{
return data[ pos + 7 ];
}
pos += length;
}
return;
};
/*****************************************************************************
*
*****************************************************************************/
x3dom.Utils.detectChannelCountPNG = function ( data )
{
if ( data.byteLength < 29 ) {return;}
// PNG header
if ( data[ 0 ] != 0x89 ) {return;}
if ( data[ 1 ] != 0x50 ) {return;}
if ( data[ 2 ] != 0x4e ) {return;}
if ( data[ 3 ] != 0x47 ) {return;}
if ( data[ 4 ] != 0x0d ) {return;}
if ( data[ 5 ] != 0x0a ) {return;}
if ( data[ 6 ] != 0x1a ) {return;}
if ( data[ 7 ] != 0x0a ) {return;}
// "IHDR"
if ( data[ 12 ] != 0x49 ) {return;}
if ( data[ 13 ] != 0x48 ) {return;}
if ( data[ 14 ] != 0x44 ) {return;}
if ( data[ 15 ] != 0x52 ) {return;}
if ( data[ 25 ] == 0 ) {return 1;}
if ( data[ 25 ] == 2 ) {return 3;}
if ( data[ 25 ] == 4 ) {return 2;}
if ( data[ 25 ] == 6 ) {return 4;}
};
/*****************************************************************************
*
*****************************************************************************/
x3dom.Utils.detectChannelCountGIF = function ( data )
{
if ( data[ 0 ] != 0x47 ) {return;}
if ( data[ 1 ] != 0x49 ) {return;}
if ( data[ 2 ] != 0x46 ) {return;}
if ( data[ 3 ] != 0x38 ) {return;}
if ( data[ 4 ] != 0x37 && data[ 4 ] != 0x39 ) {return;}
if ( data[ 5 ] != 0x61 ) {return;}
if ( data[ 10 ] & 0x01 )
{
var length = data[ 10 ] & 0xE0;
for ( var i = 13; i < length; i += 3 )
{
if ( data[ i ] != data[ i + 1 ] || data[ i ] != data[ i + 2 ] || data[ i + 1 ] != data[ i + 2 ] )
{
return 3;
}
}
return 1;
}
//TODO check local color table of the image block
};
/*****************************************************************************
* Initialize framebuffer object and associated texture(s)
*****************************************************************************/
x3dom.Utils.initFBO = function ( gl, w, h, type, mipMap, needDepthBuf, numMrt )
{
var tex = gl.createTexture();
tex.width = w;
tex.height = h;
gl.bindTexture( gl.TEXTURE_2D, tex );
gl.texImage2D( gl.TEXTURE_2D, 0, gl.RGBA, w, h, 0, gl.RGBA, type, null );
if ( mipMap )
{gl.generateMipmap( gl.TEXTURE_2D );}
gl.bindTexture( gl.TEXTURE_2D, null );
var i,
mrts = null;
if ( x3dom.caps.DRAW_BUFFERS && numMrt !== undefined )
{
mrts = [ tex ];
for ( i = 1; i < numMrt; i++ )
{
mrts[ i ] = gl.createTexture();
mrts[ i ].width = w;
mrts[ i ].height = h;
gl.bindTexture( gl.TEXTURE_2D, mrts[ i ] );
gl.texImage2D( gl.TEXTURE_2D, 0, gl.RGBA, w, h, 0, gl.RGBA, type, null );
if ( mipMap )
{gl.generateMipmap( gl.TEXTURE_2D );}
gl.bindTexture( gl.TEXTURE_2D, null );
}
}
var fbo = gl.createFramebuffer();
var dtex = null;
var rb = null;
if ( needDepthBuf )
{
if ( x3dom.caps.DEPTH_TEXTURE !== null )
{
dtex = gl.createTexture();
gl.bindTexture( gl.TEXTURE_2D, dtex );
gl.texImage2D( gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT, w, h, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_SHORT, null );
if ( mipMap )
{gl.generateMipmap( gl.TEXTURE_2D );}
gl.bindTexture( gl.TEXTURE_2D, null );
dtex.width = w;
dtex.height = h;
}
else
{
rb = gl.createRenderbuffer();
gl.bindRenderbuffer( gl.RENDERBUFFER, rb );
gl.renderbufferStorage( gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, w, h );
gl.bindRenderbuffer( gl.RENDERBUFFER, null );
}
}
gl.bindFramebuffer( gl.FRAMEBUFFER, fbo );
gl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0 );
if ( x3dom.caps.DRAW_BUFFERS && numMrt !== undefined )
{
for ( i = 1; i < numMrt; i++ )
{
gl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, gl.TEXTURE_2D, mrts[ i ], 0 );
}
}
if ( needDepthBuf && x3dom.caps.DEPTH_TEXTURE !== null )
{
gl.framebufferTexture2D( gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, dtex, 0 );
}
else
{
gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, rb );
}
var status = gl.checkFramebufferStatus( gl.FRAMEBUFFER );
if ( status != gl.FRAMEBUFFER_COMPLETE )
{
x3dom.debug.logWarning( "[Utils|InitFBO] FBO-Status: " + status );
}
gl.bindFramebuffer( gl.FRAMEBUFFER, null );
return {
fbo : fbo,
dtex : dtex,
rbo : rb,
tex : tex,
texTargets : mrts,
width : w,
height : h,
type : type,
mipMap : mipMap
};
};
/*****************************************************************************
*
*****************************************************************************/
x3dom.Utils.getFileName = function ( url )
{
var filename;
if ( url.lastIndexOf( "/" ) > -1 )
{
filename = url.substr( url.lastIndexOf( "/" ) + 1 );
}
else if ( url.lastIndexOf( "\\" ) > -1 )
{
filename = url.substr( url.lastIndexOf( "\\" ) + 1 );
}
else
{
filename = url;
}
return filename;
};
/*****************************************************************************
*
*****************************************************************************/
x3dom.Utils.isWebGL2Enabled = function ()
{
var canvas = document.createElement( "canvas" );
var webgl2 = canvas.getContext( "webgl2" ) || canvas.getContext( "experimental-webgl2" );
return ( webgl2 ) ? true : false;
};
/*****************************************************************************
*
*****************************************************************************/
x3dom.Utils.findTextureByName = function ( texture, name )
{
for ( var i = 0; i < texture.length; ++i )
{
if ( name == texture[ i ].samplerName )
{return texture[ i ];}
}
return false;
};
/*****************************************************************************
* Rescale image to given size
*****************************************************************************/
x3dom.Utils.rescaleImage = function ( image, width, height )
{
var canvas = document.createElement( "canvas" );
canvas.width = width; canvas.height = height;
canvas.getContext( "2d" ).drawImage( image,
0, 0, image.width, image.height,
0, 0, canvas.width, canvas.height );
return canvas;
};
/*****************************************************************************
* Scale image to next best power of two
*****************************************************************************/
x3dom.Utils.scaleImage = function ( image )
{
if ( !x3dom.Utils.isPowerOfTwo( image.width ) || !x3dom.Utils.isPowerOfTwo( image.height ) )
{
var canvas = document.createElement( "canvas" );
canvas.width = x3dom.Utils.nextHighestPowerOfTwo( image.width );
canvas.height = x3dom.Utils.nextHighestPowerOfTwo( image.height );
var ctx = canvas.getContext( "2d" );
ctx.drawImage( image,
0, 0, image.width, image.height,
0, 0, canvas.width, canvas.height );
image = canvas;
}
return image;
};
/*****************************************************************************
* Check if value is power of two
*****************************************************************************/
x3dom.Utils.isPowerOfTwo = function ( x )
{
return ( ( x & ( x - 1 ) ) === 0 );
};
/*****************************************************************************
* Return next highest power of two
*****************************************************************************/
x3dom.Utils.nextHighestPowerOfTwo = function ( x )
{
--x;
for ( var i = 1; i < 32; i <<= 1 )
{
x = x | x >> i;
}
return ( x + 1 );
};
/*****************************************************************************
* Return next best power of two
*****************************************************************************/
x3dom.Utils.nextBestPowerOfTwo = function ( x )
{
// use precomputed log(2.0) = 0.693147180559945
var log2x = Math.log( x ) / 0.693147180559945;
return Math.pow( 2, Math.round( log2x ) );
};
/*****************************************************************************
* Return data type size in byte
*****************************************************************************/
x3dom.Utils.getDataTypeSize = function ( type )
{
switch ( type )
{
case "Int8":
case "Uint8":
return 1;
case "Int16":
case "Uint16":
return 2;
case "Int32":
case "Uint32":
case "Float32":
return 4;
case "Float64":
default:
return 8;
}
};
/*****************************************************************************
* Return offset multiplier (Uint32 is twice as big as Uint16)
*****************************************************************************/
x3dom.Utils.getOffsetMultiplier = function ( indexType, gl )
{
switch ( indexType )
{
case gl.UNSIGNED_SHORT:
return 1;
case gl.UNSIGNED_INT:
return 2;
case gl.UNSIGNED_BYTE:
return 0.5;
default:
return 1;
}
};
/*****************************************************************************
* Return byte aware offset
*****************************************************************************/
x3dom.Utils.getByteAwareOffset = function ( offset, indexType, gl )
{
switch ( indexType )
{
case gl.UNSIGNED_SHORT:
return 2 * offset;
case gl.UNSIGNED_INT:
return 4 * offset;
case gl.UNSIGNED_BYTE:
return offset;
default:
return 2 * offset;
}
};
/*****************************************************************************
* Return this.gl-Type
*****************************************************************************/
x3dom.Utils.getVertexAttribType = function ( type, gl )
{
var dataType = gl.NONE;
switch ( type )
{
case "Int8":
dataType = gl.BYTE;
break;
case "Uint8":
dataType = gl.UNSIGNED_BYTE;
break;
case "Int16":
dataType = gl.SHORT;
break;
case "Uint16":
dataType = gl.UNSIGNED_SHORT;
break;
case "Int32":
dataType = gl.INT;
break;
case "Uint32":
dataType = gl.UNSIGNED_INT;
break;
case "Float32":
dataType = gl.FLOAT;
break;
case "Float64":
default:
x3dom.debug.logError( "Can't find this.gl data type for " + type + ", getting FLOAT..." );
dataType = gl.FLOAT;
break;
}
return dataType;
};
/*****************************************************************************
* Return TypedArray View
*****************************************************************************/
x3dom.Utils.getArrayBufferView = function ( type, buffer )
{
var array = null;
switch ( type )
{
case "Int8":
array = new Int8Array( buffer );
break;
case "Uint8":
array = new Uint8Array( buffer );
break;
case "Int16":
array = new Int16Array( buffer );
break;
case "Uint16":
array = new Uint16Array( buffer );
break;
case "Int32":
array = new Int32Array( buffer );
break;
case "Uint32":
array = new Uint32Array( buffer );
break;
case "Float32":
array = new Float32Array( buffer );
break;
case "Float64":
array = new Float64Array( buffer );
break;
default:
x3dom.debug.logError( "Can't create typed array view of type " + type + ", trying Float32..." );
array = new Float32Array( buffer );
break;
}
return array;
};
/*****************************************************************************
* Checks whether a TypedArray View Type with the given name string is unsigned
*****************************************************************************/
x3dom.Utils.isUnsignedType = function ( str )
{
return ( str == "Uint8" || str == "Uint16" || str == "Uint16" || str == "Uint32" );
};
/*****************************************************************************
* Checks for lighting
*****************************************************************************/
x3dom.Utils.checkDirtyLighting = function ( viewarea )
{
return ( viewarea.getLights().length + viewarea._scene.getNavigationInfo()._vf.headlight + ( viewarea._scene.getFog()._vf.visibilityRange > 0 ) );
};
/*****************************************************************************
* Checks for PhysicalEnvironmentLight change
*****************************************************************************/
x3dom.Utils.checkDirtyPhysicalEnvironmentLight = function ( viewarea, shaderProperties )
{
return ( !!shaderProperties.PHYSICALENVLIGHT != viewarea.hasPhysicalEnvironmentLight() );
};
/*****************************************************************************
* Checks for environment
*****************************************************************************/
x3dom.Utils.checkDirtyEnvironment = function ( viewarea, shaderProperties )
{
var environment = viewarea._scene.getEnvironment();
return ( shaderProperties.GAMMACORRECTION != environment._vf.gammaCorrectionDefault );
};
/*****************************************************************************
* Get GL min filter
*****************************************************************************/
x3dom.Utils.minFilterDic = function ( gl, minFilter )
{
switch ( minFilter.toUpperCase() )
{
case "NEAREST": return gl.NEAREST;
case "LINEAR": return gl.LINEAR;
case "NEAREST_MIPMAP_NEAREST": return gl.NEAREST_MIPMAP_NEAREST;
case "NEAREST_MIPMAP_LINEAR": return gl.NEAREST_MIPMAP_LINEAR;
case "LINEAR_MIPMAP_NEAREST": return gl.LINEAR_MIPMAP_NEAREST;
case "LINEAR_MIPMAP_LINEAR": return gl.LINEAR_MIPMAP_LINEAR;
case "AVG_PIXEL": return gl.LINEAR;
case "AVG_PIXEL_AVG_MIPMAP": return gl.LINEAR_MIPMAP_LINEAR;
case "AVG_PIXEL_NEAREST_MIPMAP": return gl.LINEAR_MIPMAP_NEAREST;
case "DEFAULT": return gl.LINEAR_MIPMAP_LINEAR;
case "FASTEST": return gl.NEAREST;
case "NEAREST_PIXEL": return gl.NEAREST;
case "NEAREST_PIXEL_AVG_MIPMAP": return gl.NEAREST_MIPMAP_LINEAR;
case "NEAREST_PIXEL_NEAREST_MIPMAP": return gl.NEAREST_MIPMAP_NEAREST;
case "NICEST": return gl.LINEAR_MIPMAP_LINEAR;
default: return gl.LINEAR;
}
};
/*****************************************************************************
* Get GL min filter
*****************************************************************************/
x3dom.Utils.minFilterDicX3D = function ( minFilter )
{
switch ( minFilter )
{
case 9728: return "NEAREST";
case 9729: return "LINEAR";
case 9984: return "NEAREST_MIPMAP_NEAREST";
case 9985: return "LINEAR_MIPMAP_NEAREST";
case 9986: return "NEAREST_MIPMAP_LINEAR";
case 9987: return "LINEAR_MIPMAP_LINEAR";
default: return "LINEAR_MIPMAP_LINEAR";
}
};
/*****************************************************************************
* Get GL mag filter
*****************************************************************************/
x3dom.Utils.magFilterDic = function ( gl, magFilter )
{
switch ( magFilter.toUpperCase() )
{
case "NEAREST": return gl.NEAREST;
case "LINEAR": return gl.LINEAR;
case "AVG_PIXEL": return gl.LINEAR;
case "DEFAULT": return gl.LINEAR;
case "FASTEST": return gl.NEAREST;
case "NEAREST_PIXEL": return gl.NEAREST;
case "NICEST": return gl.LINEAR;
default: return gl.LINEAR;
}
};
/*****************************************************************************
* Get X3D mag filter
*****************************************************************************/
x3dom.Utils.magFilterDicX3D = function ( magFilter )
{
switch ( magFilter )
{
case 9728: return "NEAREST";
case 9729: return "LINEAR";
default: return "LINEAR";
}
};
/*****************************************************************************
* Get GL boundary mode
*****************************************************************************/
x3dom.Utils.boundaryModesDicX3D = function ( mode )
{
switch ( mode )
{
case 10497: return "REPEAT";
case 33071: return "CLAMP_TO_EDGE";
case 33648: return "MIRRORED_REPEAT";
default: return "REPEAT";
}
};
/*****************************************************************************
* Get GL boundary mode
*****************************************************************************/
x3dom.Utils.boundaryModesDic = function ( gl, mode )
{
switch ( mode.toUpperCase() )
{
case "CLAMP": return gl.CLAMP_TO_EDGE;
case "CLAMP_TO_EDGE": return gl.CLAMP_TO_EDGE;
case "CLAMP_TO_BOUNDARY": return gl.CLAMP_TO_EDGE;
case "MIRRORED_REPEAT": return gl.MIRRORED_REPEAT;
case "REPEAT": return gl.REPEAT;
default: return gl.REPEAT;
}
};
/*****************************************************************************
* Get GL primitive type
*****************************************************************************/
x3dom.Utils.primTypeDic = function ( gl, type )
{
switch ( type.toUpperCase() )
{
case "POINTS": return gl.POINTS;
case "LINES": return gl.LINES;
case "LINELOOP": return gl.LINE_LOOP;
case "LINESTRIP": return gl.LINE_STRIP;
case "TRIANGLES": return gl.TRIANGLES;
case "TRIANGLESTRIP": return gl.TRIANGLE_STRIP;
case "TRIANGLEFAN": return gl.TRIANGLE_FAN;
default: return gl.TRIANGLES;
}
};
/*****************************************************************************
* Get GL depth function
*****************************************************************************/
x3dom.Utils.depthFunc = function ( gl, func )
{
switch ( func.toUpperCase() )
{
case "NEVER": return gl.NEVER;
case "ALWAYS": return gl.ALWAYS;
case "LESS": return gl.LESS;
case "EQUAL": return gl.EQUAL;
case "LEQUAL": return gl.LEQUAL;
case "GREATER": return gl.GREATER;
case "GEQUAL": return gl.GEQUAL;
case "NOTEQUAL": return gl.NOTEQUAL;
default: return gl.LEQUAL;
}
};
/*****************************************************************************
* Get GL blend function
*****************************************************************************/
x3dom.Utils.blendFunc = function ( gl, func )
{
switch ( func.toLowerCase() )
{
case "zero": return gl.ZERO;
case "one": return gl.ONE;
case "dst_color": return gl.DST_COLOR;
case "dst_alpha": return gl.DST_ALPHA;
case "src_color": return gl.SRC_COLOR;
case "src_alpha": return gl.SRC_ALPHA;
case "one_minus_dst_color": return gl.ONE_MINUS_DST_COLOR;
case "one_minus_dst_alpha": return gl.ONE_MINUS_DST_ALPHA;
case "one_minus_src_color": return gl.ONE_MINUS_SRC_COLOR;
case "one_minus_src_alpha": return gl.ONE_MINUS_SRC_ALPHA;
case "src_alpha_saturate": return gl.SRC_ALPHA_SATURATE;
case "constant_color": return gl.CONSTANT_COLOR;
case "constant_alpha": return gl.CONSTANT_ALPHA;
case "one_minus_constant_color": return gl.ONE_MINUS_CONSTANT_COLOR;
case "one_minus_constant_alpha": return gl.ONE_MINUS_CONSTANT_ALPHA;
default: return 0;
}
};
/*****************************************************************************
* Get GL blend equations
*****************************************************************************/
x3dom.Utils.blendEquation = function ( gl, func )
{
switch ( func.toLowerCase() )
{
case "func_add": return gl.FUNC_ADD;
case "func_subtract": return gl.FUNC_SUBTRACT;
case "func_reverse_subtract": return gl.FUNC_REVERSE_SUBTRACT;
case "min": return 0; //Not supported yet
case "max": return 0; //Not supported yet
case "logic_op": return 0; //Not supported yet
default: return 0;
}
};
/*****************************************************************************
*
*****************************************************************************/
x3dom.Utils.generateProperties = function ( viewarea, shape )
{
var property = {};
var geometry = shape._cf.geometry.node;
var appearance = shape._cf.appearance.node;
var texture = appearance ? appearance._cf.texture.node : null;
var material = appearance ? appearance._cf.material.node : null;
var environment = viewarea._scene.getEnvironment();
//Check if it's a composed shader
if ( appearance && appearance._shader &&
x3dom.isa( appearance._shader, x3dom.nodeTypes.ComposedShader ) )
{
property.CSHADER = appearance._shader._id; //shape._objectID;
}
else if ( geometry )
{
property.CSHADER = -1;
property.APPMAT = ( appearance && ( material || property.CSSHADER ) ) ? 1 : 0;
property.SOLID = ( shape.isSolid() ) ? 1 : 0;
property.TEXT = ( x3dom.isa( geometry, x3dom.nodeTypes.Text ) ) ? 1 : 0;
property.POPGEOMETRY = ( x3dom.isa( geometry, x3dom.nodeTypes.PopGeometry ) ) ? 1 : 0;
property.BUFFERGEOMETRY = ( x3dom.isa( geometry, x3dom.nodeTypes.BufferGeometry ) ) ? 1 : 0;
property.BINARYGEOMETRY = ( x3dom.isa( geometry, x3dom.nodeTypes.BinaryGeometry ) ) ? 1 : 0;
property.POINTLINE2D = !geometry.needLighting() ? 1 : 0;
property.VERTEXID = ( ( property.BINARYGEOMETRY ) && geometry._vf.idsPerVertex ) ? 1 : 0;
property.IS_PARTICLE = ( x3dom.isa( geometry, x3dom.nodeTypes.ParticleSet ) ) ? 1 : 0;
property.POINTPROPERTIES = ( appearance && appearance._cf.pointProperties.node ) ? 1 : 0;
property.TANGENTDATA = ( geometry._mesh._tangents[ 0 ].length > 0 && geometry._mesh._binormals[ 0 ].length > 0 ) ? 1 : 0;
property.PBR_MATERIAL = ( property.APPMAT && x3dom.isa( material, x3dom.nodeTypes.PhysicalMaterial ) ) ? 1 : 0;
property.TWOSIDEDMAT = ( property.APPMAT && x3dom.isa( material, x3dom.nodeTypes.TwoSidedMaterial ) ) ? 1 : 0;
property.SEPARATEBACKMAT = ( property.TWOSIDEDMAT && material._vf.separateBackColor ) ? 1 : 0;
property.SHADOW = ( viewarea.getLightsShadow() ) ? 1 : 0;
property.FOG = ( viewarea._scene.getFog()._vf.visibilityRange > 0 ) ? 1 : 0;
property.CSSHADER = ( appearance && appearance._shader &&
x3dom.isa( appearance._shader, x3dom.nodeTypes.CommonSurfaceShader ) ) ? 1 : 0;
property.LIGHTS = ( !property.POINTLINE2D && appearance && shape.isLit() && ( material || property.CSSHADER ) ) ?
viewarea.getLights().length + ( viewarea._scene.getNavigationInfo()._vf.headlight ) : 0;
property.TEXTURED = ( texture || property.TEXT || ( property.CSSHADER && appearance._shader.needTexcoords() ) ||
( property.PBR_MATERIAL && material.hasTextures() ) ) ? 1 : 0;
property.CUBEMAP = ( texture && x3dom.isa( texture, x3dom.nodeTypes.X3DEnvironmentTextureNode ) ) ||
( property.CSSHADER && appearance._shader.getEnvironmentMap() ) ? 1 : 0;
property.PIXELTEX = ( texture && x3dom.isa( texture, x3dom.nodeTypes.PixelTexture ) ) ? 1 : 0;
property.TEXTRAFO = ( appearance && appearance._cf.textureTransform.node ) ? 1 : 0;
property.DIFFUSEMAP = ( texture && !x3dom.isa( texture, x3dom.nodeTypes.X3DEnvironmentTextureNode ) ) ||
( property.CSSHADER && appearance._shader.getDiffuseMap() ) ||
( property.PBR_MATERIAL && material._cf.baseColorTexture.node ) ? 1 : 0;
property.NORMALMAP = ( property.CSSHADER && appearance._shader.getNormalMap() ) ||
( property.PBR_MATERIAL && material._cf.normalTexture.node ) ? 1 : 0;
property.SPECMAP = ( property.CSSHADER && appearance._shader.getSpecularMap() ) ? 1 : 0;
property.SHINMAP = ( property.CSSHADER && appearance._shader.getShininessMap() ) ? 1 : 0;
property.EMISSIVEMAP = ( property.PBR_MATERIAL && material._cf.emissiveTexture.node ) ? 1 : 0;
property.OCCLUSIONMAP = ( property.PBR_MATERIAL && material._cf.occlusionTexture.node ) ? 1 : 0;
property.DISPLACEMENTMAP = ( property.CSSHADER && appearance._shader.getDisplacementMap() ) ? 1 : 0;
property.DIFFPLACEMENTMAP = ( property.CSSHADER && appearance._shader.getDiffuseDisplacementMap() ) ? 1 : 0;
property.ALPHAMODE = ( property.PBR_MATERIAL ) ? material._vf.alphaMode : "BLEND";
property.ISROUGHNESSMETALLIC = ( property.PBR_MATERIAL && material._vf.model == "roughnessMetallic" ) ? 1 : 0;
property.ROUGHNESSMETALLICMAP = ( property.PBR_MATERIAL && material._cf.roughnessMetallicTexture.node ) ? 1 : 0;
property.SPECULARGLOSSINESSMAP = ( property.PBR_MATERIAL && material._cf.specularGlossinessTexture.node ) ? 1 : 0;
property.OCCLUSIONROUGHNESSMETALLICMAP = ( property.PBR_MATERIAL && material._cf.occlusionRoughnessMetallicTexture.node ) ? 1 : 0;
property.PHYSICALENVLIGHT = viewarea.hasPhysicalEnvironmentLight() ? 1 : 0;
property.NORMALSPACE = ( property.NORMALMAP && property.CSSHADER ) ? appearance._shader._vf.normalSpace.toUpperCase() :
( property.NORMALMAP && property.PBR_MATERIAL ) ? material._vf.normalSpace.toUpperCase() : "TANGENT";
property.BLENDING = ( property.TEXT || property.CUBEMAP || property.CSSHADER || ( property.PBR_MATERIAL ) || ( texture && texture._blending ) ) ? 1 : 0;
property.REQUIREBBOX = ( geometry._vf.coordType !== undefined && geometry._vf.coordType != "Float32" ) ? 1 : 0;
property.REQUIREBBOXNOR = ( geometry._vf.normalType !== undefined && geometry._vf.normalType != "Float32" ) ? 1 : 0;
property.REQUIREBBOXCOL = ( geometry._vf.colorType !== undefined && geometry._vf.colorType != "Float32" ) ? 1 : 0;
property.REQUIREBBOXTEX = ( geometry._vf.texCoordType !== undefined && geometry._vf.texCoordType != "Float32" ) ? 1 : 0;
property.COLCOMPONENTS = geometry._mesh._numColComponents;
property.NORCOMPONENTS = geometry._mesh._numNormComponents;
property.POSCOMPONENTS = geometry._mesh._numPosComponents;
property.SPHEREMAPPING = ( geometry._cf.texCoord !== undefined && geometry._cf.texCoord.node !== null &&
geometry._cf.texCoord.node._vf.mode &&
geometry._cf.texCoord.node._vf.mode.toLowerCase() == "sphere" ) ? 1 : 0;
property.VERTEXCOLOR = ( geometry._mesh._colors[ 0 ].length > 0 ||
( property.POPGEOMETRY && geometry.hasColor() ) ||
( property.BUFFERGEOMETRY && geometry.hasColor() ) ||
( geometry._vf.color !== undefined && geometry._vf.color.length > 0 ) ) ? 1 : 0;
property.CLIPPLANES = shape._clipPlanes.length;
property.ALPHATHRESHOLD = ( appearance ) ? appearance._vf.alphaClipThreshold.toFixed( 2 ) : 0.1;
property.MULTITEXCOORD = ( property.BUFFERGEOMETRY && geometry.hasMultiTexCoord() ) ? 1 : 0;
property.DIFFUSEMAPCHANNEL = ( property.PBR_MATERIAL && property.DIFFUSEMAP && material._cf.baseColorTexture.node._vf.channel === 1 ) ? 1 : 0;
property.NORMALMAPCHANNEL = ( property.PBR_MATERIAL && property.NORMALMAP && material._cf.normalTexture.node._vf.channel === 1 ) ? 1 : 0;
property.EMISSIVEMAPCHANNEL = ( property.PBR_MATERIAL && property.EMISSIVEMAP && material._cf.emissiveTexture.node._vf.channel === 1 ) ? 1 : 0;
property.OCCLUSIONMAPCHANNEL = ( property.PBR_MATERIAL && property.OCCLUSIONMAP && material._cf.occlusionTexture.node._vf.channel === 1 ) ? 1 : 0;
property.ROUGHNESSMETALLICMAPCHANNEL = ( property.PBR_MATERIAL && property.ROUGHNESSMETALLICMAP && material._cf.roughnessMetallicTexture.node._vf.channel === 1 ) ? 1 : 0;
property.OCCLUSIONROUGHNESSMETALLICMAPCHANNEL = ( property.PBR_MATERIAL && property.OCCLUSIONROUGHNESSMETALLICMAP && material._cf.occlusionRoughnessMetallicTexture.node._vf.channel === 1 ) ? 1 : 0;
property.SPECULARGLOSSINESSMAPCHANNEL = ( property.PBR_MATERIAL && property.SPECULARGLOSSINESSMAP && material._cf.specularGlossinessTexture.node._vf.channel === 1 ) ? 1 : 0;
property.ALPHAMASK = ( property.PBR_MATERIAL && material._vf.alphaMode == "MASK" ) ? 1 : 0;
property.UNLIT = ( property.PBR_MATERIAL && material._vf.unlit ) ? 1 : 0;
property.GAMMACORRECTION = environment._vf.gammaCorrectionDefault;
property.KHR_MATERIAL_COMMONS = 0;
}
property.toIdentifier = function ()
{
delete this.id;
var id = "";
for ( var p in this )
{
if ( this[ p ] != this.toIdentifier && this[ p ] != this.toString )
{
id += this[ p ];
}
}
this.id = id;
return id;
};
property.toString = function ()
{
var str = "";
for ( var p in this )
{
if ( this[ p ] != this.toIdentifier && this[ p ] != this.toString )
{
str += p + ": " + this[ p ] + ", ";
}
}
return str;
};
property.toIdentifier();
return property;
};
/*****************************************************************************
* Returns the linear interpolations between two values
*****************************************************************************/
x3dom.Utils.lerp = function ( value1, value2, amount )
{
amount = amount < 0 ? 0 : amount;
amount = amount > 1 ? 1 : amount;
return value1 + ( value2 - value1 ) * amount;
};
/*****************************************************************************
* Returns "shader" such that "shader.foo = [1,2,3]" magically sets the
* appropriate uniform
*****************************************************************************/
x3dom.Utils.wrapProgram = function ( gl, program, shaderID )
{
var shader = {
shaderID : shaderID,
program : program
};
shader.bind = function ()
{
gl.useProgram( program );
};
var loc = null;
var obj = null;
var i,
glErr;
// get uniforms
var numUniforms = gl.getProgramParameter( program, gl.ACTIVE_UNIFORMS );
for ( i = 0; i < numUniforms; ++i )
{
try
{
obj = gl.getActiveUniform( program, i );
}
catch ( eu )
{
if ( !obj ) {continue;}
}
glErr = gl.getError();
if ( glErr )
{
x3dom.debug.logError( "GL-Error (on searching uniforms):"
+ " name=" + obj.name
+ " type=" + obj.type
+ " size=" + obj.size
+ " Err=" + glErr );
}
loc = gl.getUniformLocation( program, obj.name );
switch ( obj.type )
{
case gl.SAMPLER_2D:
shader.__defineSetter__( obj.name,
( function ( loc ) { return function ( val ) { gl.uniform1i( loc, val ); }; } )( loc ) );
break;
case gl.SAMPLER_CUBE:
shader.__defineSetter__( obj.name,
( function ( loc ) { return function ( val ) { gl.uniform1i( loc, val ); }; } )( loc ) );
break;
case gl.BOOL:
shader.__defineSetter__( obj.name,
( function ( loc ) { return function ( val ) { gl.uniform1i( loc, val ); }; } )( loc ) );
break;
case gl.FLOAT:
/*
* Passing a MFFloat type into uniform.
* by Sofiane Benchaa, 2012.
*
* Based on OpenGL specification.
* url: http://www.opengl.org/sdk/docs/man/xhtml/glGetUniformLocation.xml
*
* excerpt : Except if the last part of name indicates a uniform variable array,
* the location of the first element of an array can be retrieved by using the name of the array,
* or by using the name appended by "[0]".
*
* Detecting the float array and extracting its uniform name without the brackets.
*/
if ( obj.name.indexOf( "[0]" ) != -1 )
{
shader.__defineSetter__( obj.name.substring( 0, obj.name.length - 3 ),
( function ( loc ) { return function ( val ) { gl.uniform1fv( loc, new Float32Array( val ) ); }; } )( loc ) );
}
else
{
shader.__defineSetter__( obj.name,
( function ( loc ) { return function ( val ) { gl.uniform1f( loc, val ); }; } )( loc ) );
}
break;
case gl.FLOAT_VEC2:
shader.__defineSetter__( obj.name,
( function ( loc ) { return function ( val ) { gl.uniform2f( loc, val[ 0 ], val[ 1 ] ); }; } )( loc ) );
break;
case gl.FLOAT_VEC3:
/* Passing arrays of vec3. see above.*/
if ( obj.name.indexOf( "[0]" ) != -1 )
{
shader.__defineSetter__( obj.name.substring( 0, obj.name.length - 3 ),
( function ( loc ) { return function ( val ) { gl.uniform3fv( loc, new Float32Array( val ) ); }; } )( loc ) );
}
else
{
shader.__defineSetter__( obj.name,
( function ( loc ) { return function ( val ) { gl.uniform3f( loc, val[ 0 ], val[ 1 ], val[ 2 ] ); }; } )( loc ) );
}
break;
case gl.FLOAT_VEC4:
shader.__defineSetter__( obj.name,
( function ( loc ) { return function ( val ) { gl.uniform4f( loc, val[ 0 ], val[ 1 ], val[ 2 ], val[ 3 ] ); }; } )( loc ) );
break;
case gl.FLOAT_MAT2:
shader.__defineSetter__( obj.name,
( function ( loc ) { return function ( val ) { gl.uniformMatrix2fv( loc, false, new Float32Array( val ) ); }; } )( loc ) );
break;
case gl.FLOAT_MAT3:
shader.__defineSetter__( obj.name,
( function ( loc ) { return function ( val ) { gl.uniformMatrix3fv( loc, false, new Float32Array( val ) ); }; } )( loc ) );
break;
case gl.FLOAT_MAT4:
shader.__defineSetter__( obj.name,
( function ( loc ) { return function ( val ) { gl.uniformMatrix4fv( loc, false, new Float32Array( val ) ); }; } )( loc ) );
break;
case gl.INT:
shader.__defineSetter__( obj.name,
( function ( loc ) { return function ( val ) { gl.uniform1i( loc, val ); }; } )( loc ) );
break;
default:
x3dom.debug.logWarning( "GLSL program variable " + obj.name + " has unknown type " + obj.type );
}
}
// get attributes
var numAttribs = gl.getProgramParameter( program, gl.ACTIVE_ATTRIBUTES );
for ( i = 0; i < numAttribs; ++i )
{
try
{
obj = gl.getActiveAttrib( program, i );
}
catch ( ea )
{
if ( !obj ) {continue;}
}
glErr = gl.getError();
if ( glErr )
{
x3dom.debug.logError( "GL-Error (on searching attributes):"
+ " name=" + obj.name
+ " type=" + obj.type
+ " size=" + obj.size
+ " Err=" + glErr );
}
loc = gl.getAttribLocation( program, obj.name );
shader[ obj.name ] = loc;
}
return shader;
};
/**
* Converts a UTF-8 Uint8Array to a string
* @param {String} uri_string
* @returns {boolean}
*/
x3dom.Utils.arrayBufferToJSON = function ( array, offset, length )
{
offset = ( offset != undefined ) ? offset : 0;
length = ( length != undefined ) ? length : array.length;
var out,
i,
len,
c,
char2,
char3;
out = "";
len = length;
i = offset;
while ( i < len )
{
c = array[ i++ ];
switch ( c >> 4 )
{
case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
// 0xxxxxxx
out += String.fromCharCode( c );
break;
case 12: case 13:
// 110x xxxx 10xx xxxx
char2 = array[ i++ ];
out += String.fromCharCode( ( ( c & 0x1F ) << 6 ) | ( char2 & 0x3F ) );
break;
case 14:
// 1110 xxxx 10xx xxxx 10xx xxxx
char2 = array[ i++ ];
char3 = array[ i++ ];
out += String.fromCharCode( ( ( c & 0x0F ) << 12 ) |
( ( char2 & 0x3F ) << 6 ) |
( ( char3 & 0x3F ) << 0 ) );
break;
}
}
return JSON.parse( out );
};
x3dom.Utils.arrayBufferToObjectURL = function ( buffer, mimetype )
{
return URL.createObjectURL( new Blob( [ new Uint8Array( buffer ) ], {type: mimetype} ) );
};
x3dom.Utils.dataURIToObjectURL = function ( dataURI )
{
if ( dataURI.indexOf( "data:" ) == -1 )
{
return dataURI;
}
var parts = dataURI.split( "," );
var mimetype = parts[ 0 ].split( ":" )[ 1 ].split( ";" )[ 0 ];
var data = parts[ 1 ];
var binaryString = window.atob( data );
var byteLength = binaryString.length;
var bytes = new Uint8Array( byteLength );
for ( var i = 0; i < byteLength; i++ )
{
bytes[ i ] = binaryString.charCodeAt( i );
}
return URL.createObjectURL( new Blob( [ bytes ], {type: mimetype} ) );
};
x3dom.Utils.arrayBufferToDataURL = function ( buffer, mimetype )
{
var binary = "";
var bytes = new Uint8Array( buffer );
for ( var i = 0; i < bytes.byteLength; i++ )
{
binary += String.fromCharCode( bytes[ i ] );
}
return "data:" + mimetype + ";base64," + window.btoa( binary );
};
/**
* Matches a given URI with document.location. If domain, port and protocol are the same SOP won't forbid access to the resource.
* @param {String} uri_string
* @returns {boolean}
*/
x3dom.Utils.forbiddenBySOP = function ( uri_string )
{
uri_string = uri_string.toLowerCase();
// scheme ":" hier-part [ "?" query ] [ "#" fragment ]
var Scheme_AuthorityPQF = uri_string.split( "//" ); //Scheme and AuthorityPathQueryFragment
var Scheme,
AuthorityPQF,
Authority,
UserInfo_HostPort,
HostPort,
Host_Port,
Port,
Host;
var originPort = document.location.port === "" ? "80" : document.location.port;
if ( Scheme_AuthorityPQF.length === 2 )
{ // if there is '//' authority is given;
Scheme = Scheme_AuthorityPQF[ 0 ];
AuthorityPQF = Scheme_AuthorityPQF[ 1 ];
/*
* The authority component is preceded by a double slash ("//") and is
* terminated by the next slash ("/"), question mark ("?"), or number
* sign ("#") character, or by the end of the URI.
*/
Authority = AuthorityPQF.split( "/" )[ 0 ].split( "?" )[ 0 ].split( "#" )[ 0 ];
//authority = [ userinfo "@" ] host [ ":" port ]
UserInfo_HostPort = Authority.split( "@" );
if ( UserInfo_HostPort.length === 1 )
{ //No Userinfo given
HostPort = UserInfo_HostPort[ 0 ];
}
else
{
HostPort = UserInfo_HostPort[ 1 ];
}
Host_Port = HostPort.split( ":" );
Host = Host_Port[ 0 ];
Port = Host_Port[ 1 ];
} // else will return false for an invalid URL or URL without authority
Port = Port || originPort;
Host = Host || document.location.host;
Scheme = Scheme || document.location.protocol;
return !( Port === originPort && Host === document.location.host && Scheme === document.location.protocol );
};
/*
* X3DOM JavaScript Library
* http://www.x3dom.org
*
* (C)2009 Fraunhofer IGD, Darmstadt, Germany
* Dual licensed under the MIT and GPL
*
* Based on code originally provided by
* Philip Taylor: http://philip.html5.org
*/
/**
* States namespace
*/
x3dom.States = function ( x3dElem )
{
var that = this;
this.active = false;
this.viewer = document.createElement( "div" );
this.viewer.id = "x3dom-state-viewer";
var title = document.createElement( "div" );
title.className = "x3dom-states-head";
title.appendChild( document.createTextNode( "x3dom" ) );
var subTitle = document.createElement( "span" );
subTitle.className = "x3dom-states-head2";
subTitle.appendChild( document.createTextNode( "stats" ) );
title.appendChild( subTitle );
this.renderMode = document.createElement( "div" );
this.renderMode.className = "x3dom-states-rendermode-hardware";
this.measureList = document.createElement( "ul" );
this.measureList.className = "x3dom-states-list";
this.infoList = document.createElement( "ul" );
this.infoList.className = "x3dom-states-list";
this.requestList = document.createElement( "ul" );
this.requestList.className = "x3dom-states-list";
//this.viewer.appendChild(title);
this.viewer.appendChild( this.renderMode );
this.viewer.appendChild( this.measureList );
this.viewer.appendChild( this.infoList );
this.viewer.appendChild( this.requestList );
/**
* Disable the context menu
*/
this.disableContextMenu = function ( e )
{
e.preventDefault();
e.stopPropagation();
e.returnValue = false;
return false;
};
/**
* Add a seperator for thousands to the string
*/
this.thousandSeperator = function ( value )
{
return value.toString().replace( /\B(?=(\d{3})+(?!\d))/g, "," );
};
/**
* Return numerical value to fixed length
*/
this.toFixed = function ( value )
{
var fixed = ( value < 1 ) ? 2 : ( value < 10 ) ? 2 : 2;
return value.toFixed( fixed );
};
/**
*
*/
this.addItem = function ( list, key, value )
{
var item = document.createElement( "li" );
item.className = "x3dom-states-item";
var keyDiv = document.createElement( "div" );
keyDiv.className = "x3dom-states-item-title";
keyDiv.appendChild( document.createTextNode( key ) );
var valueDiv = document.createElement( "div" );
valueDiv.className = "x3dom-states-item-value";
valueDiv.appendChild( document.createTextNode( value ) );
item.appendChild( keyDiv );
item.appendChild( valueDiv );
list.appendChild( item );
};
/**
* Update the states.
*/
this.update = function ()
{
if ( !x3dElem.runtime && this.updateMethodID !== undefined )
{
clearInterval( this.updateMethodID );
return;
}
var infos = x3dElem.runtime.states.infos;
var measurements = x3dElem.runtime.states.measurements;
var renderMode = x3dom.caps.RENDERMODE;
if ( renderMode == "HARDWARE" )
{
this.renderMode.innerHTML = "Hardware-Rendering";
this.renderMode.className = "x3dom-states-rendermode-hardware";
}
else if ( renderMode == "SOFTWARE" )
{
this.renderMode.innerHTML = "Software-Rendering";
this.renderMode.className = "x3dom-states-rendermode-software";
}
//Clear measure list
this.measureList.innerHTML = "";
//Create list items
for ( var m in measurements )
{
if ( measurements.hasOwnProperty( m ) )
{
this.addItem( this.measureList, m, this.toFixed( measurements[ m ] ) );
}
}
//Clear info list
this.infoList.innerHTML = "";
//Create list items
for ( var i in infos )
{
if ( infos.hasOwnProperty( i ) )
{
this.addItem( this.infoList, i, this.thousandSeperator( infos[ i ] ) );
}
}
//Clear request list
this.requestList.innerHTML = "";
this.addItem( this.requestList, "#ACTIVE", x3dom.RequestManager.activeRequests.length );
this.addItem( this.requestList, "#TOTAL", x3dom.RequestManager.totalRequests );
this.addItem( this.requestList, "#LOADED", x3dom.RequestManager.loadedRequests );
this.addItem( this.requestList, "#FAILED", x3dom.RequestManager.failedRequests );
};
this.updateMethodID = window.setInterval( function ()
{
that.update();
}, 1000 );
this.viewer.addEventListener( "contextmenu", that.disableContextMenu );
};
/**
* Display the states
*/
x3dom.States.prototype.display = function ( value )
{
this.active = ( value !== undefined ) ? value : !this.active;
this.viewer.style.display = ( this.active ) ? "block" : "none";
};
/*
* X3DOM JavaScript Library
* http://www.x3dom.org
*
* (C)2009 Fraunhofer IGD, Darmstadt, Germany
* Dual licensed under the MIT and GPL
*
* Based on code originally provided by
* Philip Taylor: http://philip.html5.org
*/
/**
* Manage all the GL-States and try to reduce the state changes
*/
x3dom.StateManager = function ( ctx3d )
{
//Our GL-Context
this.gl = ctx3d;
//Hold all the active states
this.states = [];
//Initialize States
this.initStates();
};
/*
* Initialize States
*/
x3dom.StateManager.prototype.initStates = function ()
{
//Initialize Shader states
this.states[ "shaderID" ] = null;
//Initialize Framebuffer-Operation states
this.states[ "colorMask" ] = {red: null, green: null, blue: null, alpha: null};
this.states[ "depthMask" ] = null;
this.states[ "stencilMask" ] = null;
//Initialize Rasterization states
this.states[ "cullFace" ] = null;
this.states[ "frontFace" ] = null;
this.states[ "lineWidth" ] = null;
//Initialize Per-Fragment-Operation states
this.states[ "blendColor" ] = {red: null, green: null, blue: null, alpha: null};
this.states[ "blendEquation" ] = null;
this.states[ "blendEquationSeparate" ] = {modeRGB: null, modeAlpha: null};
this.states[ "blendFunc" ] = {sfactor: null, dfactor: null};
this.states[ "blendFuncSeparate" ] = {srcRGB: null, dstRGB: null, srcAlpha: null, dstAlpha: null};
this.states[ "depthFunc" ] = null;
//Initialize View and Clip states
this.states[ "viewport" ] = {x: null, y: null, width: null, height: null};
this.states[ "depthRange" ] = {zNear: null, zFar: null};
//TODO more states (e.g. stencil, texture, ...)
};
/*
* Only bind program if different (returns true if changed)
*/
x3dom.StateManager.prototype.useProgram = function ( shader )
{
if ( this.states[ "shaderID" ] != shader.shaderID )
{
this.gl.useProgram( shader.program );
this.states[ "shaderID" ] = shader.shaderID;
return true;
}
return false;
};
/*
* Unset active program for clean init state
*/
x3dom.StateManager.prototype.unsetProgram = function ()
{
this.states[ "shaderID" ] = null;
};
/*
* Enable GL capabilities
*/
x3dom.StateManager.prototype.enable = function ( cap )
{
if ( this.states[ cap ] !== true )
{
this.gl.enable( cap );
this.states[ cap ] = true;
}
};
/*
* Disable GL capabilities
*/
x3dom.StateManager.prototype.disable = function ( cap )
{
if ( this.states[ cap ] !== false )
{
this.gl.disable( cap );
this.states[ cap ] = false;
}
};
/*
* Enable and disable writing of frame buffer color components
*/
x3dom.StateManager.prototype.colorMask = function ( red, green, blue, alpha )
{
if ( this.states[ "colorMask" ].red != red ||
this.states[ "colorMask" ].green != green ||
this.states[ "colorMask" ].blue != blue ||
this.states[ "colorMask" ].alpha != alpha )
{
this.gl.colorMask( red, green, blue, alpha );
this.states[ "colorMask" ].red = red;
this.states[ "colorMask" ].green = green;
this.states[ "colorMask" ].blue = blue;
this.states[ "colorMask" ].alpha = alpha;
}
};
/*
* Sets whether or not you can write to the depth buffer.
*/
x3dom.StateManager.prototype.depthMask = function ( flag )
{
if ( this.states[ "depthMask" ] != flag )
{
this.gl.depthMask( flag );
this.states[ "depthMask" ] = flag;
}
};
/*
* Control the front and back writing of individual bits in the stencil planes
*/
x3dom.StateManager.prototype.stencilMask = function ( mask )
{
if ( this.states[ "stencilMask" ] != mask )
{
this.gl.stencilMask( mask );
this.states[ "stencilMask" ] = mask;
}
};
/*
* Specify whether front- or back-facing facets can be culled
*/
x3dom.StateManager.prototype.cullFace = function ( mode )
{
if ( this.states[ "cullFace" ] != mode )
{
this.gl.cullFace( mode );
this.states[ "cullFace" ] = mode;
}
};
/*
* Define front- and back-facing polygons
*/
x3dom.StateManager.prototype.frontFace = function ( mode )
{
if ( this.states[ "frontFace" ] != mode )
{
this.gl.frontFace( mode );
this.states[ "frontFace" ] = mode;
}
};
/*
* Specify the width of rasterized lines
*/
x3dom.StateManager.prototype.lineWidth = function ( width )
{
width = ( width <= 1 ) ? 1 : width;
if ( this.states[ "lineWidth" ] != width )
{
this.gl.lineWidth( width );
this.states[ "lineWidth" ] = width;
}
};
/*
* Set the blend color
*/
x3dom.StateManager.prototype.blendColor = function ( red, green, blue, alpha )
{
if ( this.states[ "blendColor" ].red != red ||
this.states[ "blendColor" ].green != green ||
this.states[ "blendColor" ].blue != blue ||
this.states[ "blendColor" ].alpha != alpha )
{
this.gl.blendColor( red, green, blue, alpha );
this.states[ "blendColor" ].red = red;
this.states[ "blendColor" ].green = green;
this.states[ "blendColor" ].blue = blue;
this.states[ "blendColor" ].alpha = alpha;
}
};
/*
* Specify the equation used for both the RGB blend equation and the Alpha blend equation
*/
x3dom.StateManager.prototype.blendEquation = function ( mode )
{
if ( mode && this.states[ "blendEquation" ] != mode )
{
this.gl.blendEquation( mode );
this.states[ "blendEquation" ] = mode;
}
};
/*
* set the RGB blend equation and the alpha blend equation separately
*/
x3dom.StateManager.prototype.blendEquationSeparate = function ( modeRGB, modeAlpha )
{
if ( this.states[ "blendEquationSeparate" ].modeRGB != modeRGB ||
this.states[ "blendEquationSeparate" ].modeAlpha != modeAlpha )
{
this.gl.blendEquationSeparate( modeRGB, modeAlpha );
this.states[ "blendEquationSeparate" ].modeRGB = modeRGB;
this.states[ "blendEquationSeparate" ].modeAlpha = modeAlpha;
}
};
/*
* Specify pixel arithmetic
*/
x3dom.StateManager.prototype.blendFunc = function ( sfactor, dfactor )
{
if ( this.states[ "blendFunc" ].sfactor != sfactor ||
this.states[ "blendFunc" ].dfactor != dfactor )
{
this.gl.blendFunc( sfactor, dfactor );
this.states[ "blendFunc" ].sfactor = sfactor;
this.states[ "blendFunc" ].dfactor = dfactor;
}
};
/*
* Specify pixel arithmetic for RGB and alpha components separately
*/
x3dom.StateManager.prototype.blendFuncSeparate = function ( srcRGB, dstRGB, srcAlpha, dstAlpha )
{
if ( this.states[ "blendFuncSeparate" ].srcRGB != srcRGB ||
this.states[ "blendFuncSeparate" ].dstRGB != dstRGB ||
this.states[ "blendFuncSeparate" ].srcAlpha != srcAlpha ||
this.states[ "blendFuncSeparate" ].dstAlpha != dstAlpha )
{
this.gl.blendFuncSeparate( srcRGB, dstRGB, srcAlpha, dstAlpha );
this.states[ "blendFuncSeparate" ].srcRGB = srcRGB;
this.states[ "blendFuncSeparate" ].dstRGB = dstRGB;
this.states[ "blendFuncSeparate" ].srcAlpha = srcAlpha;
this.states[ "blendFuncSeparate" ].dstAlpha = dstAlpha;
}
};
/*
* Specify the value used for depth buffer comparisons
*/
x3dom.StateManager.prototype.depthFunc = function ( func )
{
if ( this.states[ "depthFunc" ] != func )
{
this.gl.depthFunc( func );
this.states[ "depthFunc" ] = func;
}
};
/*
* Specify the value used for depth buffer comparisons
*/
x3dom.StateManager.prototype.depthRange = function ( zNear, zFar )
{
if ( zNear < 0 || zFar < 0 || zNear > zFar )
{
return; // do noting and leave default values
}
zNear = ( zNear > 1 ) ? 1 : zNear;
zFar = ( zFar > 1 ) ? 1 : zFar;
if ( this.states[ "depthRange" ].zNear != zNear || this.states[ "depthRange" ].zFar != zFar )
{
this.gl.depthRange( zNear, zFar );
this.states[ "depthRange" ].zNear = zNear;
this.states[ "depthRange" ].zFar = zFar;
}
};
/*
* Set the viewport
*/
x3dom.StateManager.prototype.viewport = function ( x, y, width, height )
{
if ( this.states[ "viewport" ].x != x ||
this.states[ "viewport" ].y != y ||
this.states[ "viewport" ].width != width ||
this.states[ "viewport" ].height != height )
{
this.gl.viewport( x, y, width, height );
this.states[ "viewport" ].x = x;
this.states[ "viewport" ].y = y;
this.states[ "viewport" ].width = width;
this.states[ "viewport" ].height = height;
}
};
/*
* Bind a framebuffer to a framebuffer target
*/
x3dom.StateManager.prototype.bindFramebuffer = function ( target, framebuffer )
{
this.gl.bindFramebuffer( target, framebuffer );
this.initStates();
};
/*
* X3DOM JavaScript Library
* http://www.x3dom.org
*
* (C)2009 Fraunhofer IGD, Darmstadt, Germany
* Dual licensed under the MIT and GPL
*
* Based on code originally provided by
* Philip Taylor: http://philip.html5.org
*/
/** used from within gfx_webgl.js */
x3dom.BinaryContainerLoader = {
outOfMemory : false, // try to prevent browser crashes
checkError : function ( gl )
{
var glErr = gl.getError();
if ( glErr )
{
if ( glErr == gl.OUT_OF_MEMORY )
{
this.outOfMemory = true;
x3dom.debug.logError( "GL-Error " + glErr + " on loading binary container (out of memory)." );
console.error( "WebGL: OUT_OF_MEMORY" );
}
else
{
x3dom.debug.logError( "GL-Error " + glErr + " on loading binary container." );
}
}
}
};
/** setup/download binary geometry */
x3dom.BinaryContainerLoader.setupBinGeo = function ( shape, sp, gl, viewarea, currContext )
{
if ( this.outOfMemory )
{
return;
}
var t00 = Date.now();
var that = this;
var binGeo = shape._cf.geometry.node;
// 0 := no BG, 1 := indexed BG, -1 := non-indexed BG
shape._webgl.binaryGeometry = -1;
shape._webgl.internalDownloadCount = ( ( binGeo._vf.index.length > 0 ) ? 1 : 0 ) +
( ( binGeo._hasStrideOffset && binGeo._vf.coord.length > 0 ) ? 1 : 0 ) +
( ( !binGeo._hasStrideOffset && binGeo._vf.coord.length > 0 ) ? 1 : 0 ) +
( ( !binGeo._hasStrideOffset && binGeo._vf.normal.length > 0 ) ? 1 : 0 ) +
( ( !binGeo._hasStrideOffset && binGeo._vf.texCoord.length > 0 ) ? 1 : 0 ) +
( ( !binGeo._hasStrideOffset && binGeo._vf.color.length > 0 ) ? 1 : 0 );
var createTriangleSoup = ( binGeo._vf.normalPerVertex == false ) ||
( ( binGeo._vf.index.length > 0 ) && ( binGeo._vf.indexType == "Int32" ||
( binGeo._vf.indexType == "Uint32" && !x3dom.caps.INDEX_UINT ) ) );
shape._webgl.makeSeparateTris = {
index : null,
coord : null,
normal : null,
texCoord : null,
color : null,
pushBuffer : function ( name, buf )
{
this[ name ] = buf;
if ( --shape._webgl.internalDownloadCount == 0 )
{
if ( this.coord )
{this.createMesh();}
shape._nameSpace.doc.needRender = true;
}
if ( --shape._nameSpace.doc.downloadCount == 0 )
{shape._nameSpace.doc.needRender = true;}
},
createMesh : function ()
{
var geoNode = binGeo;
if ( geoNode._hasStrideOffset )
{
x3dom.debug.logError( geoNode._vf.indexType +
" index type and per-face normals not supported for interleaved arrays." );
return;
}
for ( var k = 0; k < shape._webgl.primType.length; k++ )
{
if ( shape._webgl.primType[ k ] == gl.TRIANGLE_STRIP )
{
x3dom.debug.logError( "makeSeparateTris: triangle strips not yet supported for per-face normals." );
return;
}
}
var attribTypeStr = geoNode._vf.coordType;
shape._webgl.coordType = x3dom.Utils.getVertexAttribType( attribTypeStr, gl );
// remap vertex data
var bgCenter,
bgSize,
bgPrecisionMax;
if ( shape._webgl.coordType != gl.FLOAT )
{
if ( geoNode._mesh._numPosComponents == 4 &&
x3dom.Utils.isUnsignedType( geoNode._vf.coordType ) )
{bgCenter = x3dom.fields.SFVec3f.copy( geoNode.getMin() );}
else
{bgCenter = x3dom.fields.SFVec3f.copy( geoNode._vf.position );}
bgSize = x3dom.fields.SFVec3f.copy( geoNode._vf.size );
bgPrecisionMax = geoNode.getPrecisionMax( "coordType" );
}
else
{
bgCenter = new x3dom.fields.SFVec3f( 0, 0, 0 );
bgSize = new x3dom.fields.SFVec3f( 1, 1, 1 );
bgPrecisionMax = 1.0;
}
// check types
var dataLen = shape._coordStrideOffset[ 0 ] / x3dom.Utils.getDataTypeSize( geoNode._vf.coordType );
dataLen = ( dataLen == 0 ) ? 3 : dataLen;
x3dom.debug.logWarning( "makeSeparateTris.createMesh called with coord length " + dataLen );
if ( this.color && dataLen != shape._colorStrideOffset[ 0 ] / x3dom.Utils.getDataTypeSize( geoNode._vf.colorType ) )
{
this.color = null;
x3dom.debug.logWarning( "Color format not supported." );
}
var texDataLen = this.texCoord ? ( shape._texCoordStrideOffset[ 0 ] /
x3dom.Utils.getDataTypeSize( geoNode._vf.texCoordType ) ) : 0;
// set data types
//geoNode._vf.coordType = "Float32";
geoNode._vf.normalType = "Float32";
//shape._webgl.coordType = gl.FLOAT;
shape._webgl.normalType = gl.FLOAT;
//geoNode._mesh._numPosComponents = 3;
geoNode._mesh._numNormComponents = 3;
//shape._coordStrideOffset = [0, 0];
shape._normalStrideOffset = [ 0, 0 ];
// create non-indexed mesh
var posBuf = [],
normBuf = [],
texcBuf = [],
colBuf = [],
i,
j,
l,
n = this.index ? ( this.index.length - 2 ) : ( this.coord.length / 3 - 2 );
for ( i = 0; i < n; i += 3 )
{
j = dataLen * ( this.index ? this.index[ i ] : i );
var p0 = new x3dom.fields.SFVec3f( bgSize.x * this.coord[ j ] / bgPrecisionMax,
bgSize.y * this.coord[ j + 1 ] / bgPrecisionMax,
bgSize.z * this.coord[ j + 2 ] / bgPrecisionMax );
// offset irrelevant for normal calculation
//p0 = bgCenter.add(p0);
posBuf.push( this.coord[ j ] );
posBuf.push( this.coord[ j + 1 ] );
posBuf.push( this.coord[ j + 2 ] );
if ( dataLen > 3 ) {posBuf.push( this.coord[ j + 3 ] );}
if ( this.color )
{
colBuf.push( this.color[ j ] );
colBuf.push( this.color[ j + 1 ] );
colBuf.push( this.color[ j + 2 ] );
if ( dataLen > 3 ) {colBuf.push( this.color[ j + 3 ] );}
}
if ( this.texCoord )
{
l = texDataLen * ( this.index ? this.index[ i ] : i );
texcBuf.push( this.texCoord[ l ] );
texcBuf.push( this.texCoord[ l + 1 ] );
if ( texDataLen > 3 )
{
texcBuf.push( this.texCoord[ l + 2 ] );
texcBuf.push( this.texCoord[ l + 3 ] );
}
}
j = dataLen * ( this.index ? this.index[ i + 1 ] : i + 1 );
var p1 = new x3dom.fields.SFVec3f( bgSize.x * this.coord[ j ] / bgPrecisionMax,
bgSize.y * this.coord[ j + 1 ] / bgPrecisionMax,
bgSize.z * this.coord[ j + 2 ] / bgPrecisionMax );
//p1 = bgCenter.add(p1);
posBuf.push( this.coord[ j ] );
posBuf.push( this.coord[ j + 1 ] );
posBuf.push( this.coord[ j + 2 ] );
if ( dataLen > 3 ) {posBuf.push( this.coord[ j + 3 ] );}
if ( this.color )
{
colBuf.push( this.color[ j ] );
colBuf.push( this.color[ j + 1 ] );
colBuf.push( this.color[ j + 2 ] );
if ( dataLen > 3 ) {colBuf.push( this.color[ j + 3 ] );}
}
if ( this.texCoord )
{
l = texDataLen * ( this.index ? this.index[ i + 1 ] : i + 1 );
texcBuf.push( this.texCoord[ l ] );
texcBuf.push( this.texCoord[ l + 1 ] );
if ( texDataLen > 3 )
{
texcBuf.push( this.texCoord[ l + 2 ] );
texcBuf.push( this.texCoord[ l + 3 ] );
}
}
j = dataLen * ( this.index ? this.index[ i + 2 ] : i + 2 );
var p2 = new x3dom.fields.SFVec3f( bgSize.x * this.coord[ j ] / bgPrecisionMax,
bgSize.y * this.coord[ j + 1 ] / bgPrecisionMax,
bgSize.z * this.coord[ j + 2 ] / bgPrecisionMax );
//p2 = bgCenter.add(p2);
posBuf.push( this.coord[ j ] );
posBuf.push( this.coord[ j + 1 ] );
posBuf.push( this.coord[ j + 2 ] );
if ( dataLen > 3 ) {posBuf.push( this.coord[ j + 3 ] );}
if ( this.color )
{
colBuf.push( this.color[ j ] );
colBuf.push( this.color[ j + 1 ] );
colBuf.push( this.color[ j + 2 ] );
if ( dataLen > 3 ) {colBuf.push( this.color[ j + 3 ] );}
}
if ( this.texCoord )
{
l = texDataLen * ( this.index ? this.index[ i + 2 ] : i + 2 );
texcBuf.push( this.texCoord[ l ] );
texcBuf.push( this.texCoord[ l + 1 ] );
if ( texDataLen > 3 )
{
texcBuf.push( this.texCoord[ l + 2 ] );
texcBuf.push( this.texCoord[ l + 3 ] );
}
}
var a = p0.subtract( p1 );
var b = p1.subtract( p2 );
var norm = a.cross( b ).normalize();
for ( j = 0; j < 3; j++ )
{
normBuf.push( norm.x );
normBuf.push( norm.y );
normBuf.push( norm.z );
}
}
// coordinates
var buffer = gl.createBuffer();
shape._webgl.buffers[ x3dom.BUFFER_IDX.POSITION ] = buffer;
gl.bindBuffer( gl.ARRAY_BUFFER, buffer );
gl.bufferData( gl.ARRAY_BUFFER,
x3dom.Utils.getArrayBufferView( geoNode._vf.coordType, posBuf ), gl.STATIC_DRAW );
gl.vertexAttribPointer( sp.position, geoNode._mesh._numPosComponents,
shape._webgl.coordType, false,
shape._coordStrideOffset[ 0 ], shape._coordStrideOffset[ 1 ] );
gl.enableVertexAttribArray( sp.position );
// normals
buffer = gl.createBuffer();
shape._webgl.buffers[ x3dom.BUFFER_IDX.NORMAL ] = buffer;
gl.bindBuffer( gl.ARRAY_BUFFER, buffer );
gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( normBuf ), gl.STATIC_DRAW );
gl.vertexAttribPointer( sp.normal, geoNode._mesh._numNormComponents,
shape._webgl.normalType, false,
shape._normalStrideOffset[ 0 ], shape._normalStrideOffset[ 1 ] );
gl.enableVertexAttribArray( sp.normal );
// tex coords
if ( this.texCoord )
{
buffer = gl.createBuffer();
shape._webgl.buffers[ x3dom.BUFFER_IDX.TEXCOORD ] = buffer;
gl.bindBuffer( gl.ARRAY_BUFFER, buffer );
gl.bufferData( gl.ARRAY_BUFFER,
x3dom.Utils.getArrayBufferView( geoNode._vf.texCoordType, texcBuf ),
gl.STATIC_DRAW );
gl.vertexAttribPointer( sp.texcoord, geoNode._mesh._numTexComponents,
shape._webgl.texCoordType, false,
shape._texCoordStrideOffset[ 0 ], shape._texCoordStrideOffset[ 1 ] );
gl.enableVertexAttribArray( sp.texcoord );
}
// colors
if ( this.color )
{
buffer = gl.createBuffer();
shape._webgl.buffers[ x3dom.BUFFER_IDX.COLOR ] = buffer;
gl.bindBuffer( gl.ARRAY_BUFFER, buffer );
gl.bufferData( gl.ARRAY_BUFFER,
x3dom.Utils.getArrayBufferView( geoNode._vf.colorType, colBuf ),
gl.STATIC_DRAW );
gl.vertexAttribPointer( sp.color, geoNode._mesh._numColComponents,
shape._webgl.colorType, false,
shape._colorStrideOffset[ 0 ], shape._colorStrideOffset[ 1 ] );
gl.enableVertexAttribArray( sp.color );
}
// adjust sizes
geoNode._vf.vertexCount = [];
geoNode._vf.vertexCount[ 0 ] = posBuf.length / dataLen;
geoNode._mesh._numCoords = geoNode._vf.vertexCount[ 0 ];
geoNode._mesh._numFaces = geoNode._vf.vertexCount[ 0 ] / 3;
shape._webgl.primType = [];
shape._webgl.primType[ 0 ] = gl.TRIANGLES;
// cleanup
posBuf = null;
normBuf = null;
texcBuf = null;
colBuf = null;
this.index = null;
this.coord = null;
this.normal = null;
this.texCoord = null;
this.color = null;
that.checkError( gl );
// recreate shader
delete shape._webgl.shader;
shape._webgl.shader = currContext.cache.getDynamicShader( gl, viewarea, shape );
}
};
// index
if ( binGeo._vf.index.length > 0 )
{
shape._webgl.binaryGeometry = 1; // indexed BG
var xmlhttp0 = new XMLHttpRequest();
xmlhttp0.open( "GET", shape._nameSpace.getURL( binGeo._vf.index ), true );
xmlhttp0.responseType = "arraybuffer";
shape._nameSpace.doc.incrementDownloads();
//xmlhttp0.send(null);
x3dom.RequestManager.addRequest( xmlhttp0 );
xmlhttp0.onload = function ()
{
shape._nameSpace.doc.decrementDownloads();
shape._webgl.internalDownloadCount -= 1;
if ( xmlhttp0.status != 200 )
{
x3dom.debug.logError( "XHR1/ index load failed with status: " + xmlhttp0.status );
return;
}
if ( !shape._webgl )
{return;}
if ( binGeo._vf.compressed )
{
x3dom.debug.logError( "x3dom 1.8.2+ do not support compressed BinaryGeometries anymore" );
return;
}
var XHR_buffer = xmlhttp0.response;
var geoNode = binGeo;
var attribTypeStr = geoNode._vf.indexType; //"Uint16"
var indexArray = x3dom.Utils.getArrayBufferView( attribTypeStr, XHR_buffer );
if ( createTriangleSoup )
{
shape._webgl.makeSeparateTris.pushBuffer( "index", indexArray );
return;
}
var indicesBuffer = gl.createBuffer();
if ( x3dom.caps.INDEX_UINT && attribTypeStr == "Uint32" )
{
//indexArray is Uint32Array
shape._webgl.indexType = gl.UNSIGNED_INT;
}
else
{
//indexArray is Uint16Array
shape._webgl.indexType = gl.UNSIGNED_SHORT;
}
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, indicesBuffer );
gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, indexArray, gl.STATIC_DRAW );
gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, null );
// Test reading Data
//x3dom.debug.logWarning("arraybuffer[0]="+indexArray[0]+"; n="+indexArray.length);
if ( geoNode._vf.vertexCount[ 0 ] == 0 )
{geoNode._vf.vertexCount[ 0 ] = indexArray.length;}
geoNode._mesh._numFaces = 0;
for ( var i = 0; i < geoNode._vf.vertexCount.length; i++ )
{
if ( shape._webgl.primType[ i ] == gl.TRIANGLE_STRIP )
{geoNode._mesh._numFaces += geoNode._vf.vertexCount[ i ] - 2;}
else
{geoNode._mesh._numFaces += geoNode._vf.vertexCount[ i ] / 3;}
}
indexArray = null;
if ( shape._webgl.internalDownloadCount == 0 )
{
shape._nameSpace.doc.needRender = true;
}
that.checkError( gl );
var t11 = Date.now() - t00;
x3dom.debug.logInfo( "XHR0/ index load time: " + t11 + " ms" );
shape._webgl.buffers[ x3dom.BUFFER_IDX.INDEX ] = indicesBuffer;
};
}
// interleaved array -- assume all attributes are given in one single array buffer
if ( binGeo._hasStrideOffset && binGeo._vf.coord.length > 0 )
{
var xmlhttp = new XMLHttpRequest();
xmlhttp.open( "GET", shape._nameSpace.getURL( binGeo._vf.coord ), true );
xmlhttp.responseType = "arraybuffer";
shape._nameSpace.doc.incrementDownloads();
//xmlhttp.send(null);
x3dom.RequestManager.addRequest( xmlhttp );
xmlhttp.onload = function ()
{
shape._nameSpace.doc.decrementDownloads();
shape._webgl.internalDownloadCount -= 1;
if ( xmlhttp.status != 200 )
{
x3dom.debug.logError( "XHR1/ interleaved array load failed with status: " + xmlhttp.status );
return;
}
if ( !shape._webgl )
{return;}
if ( binGeo._vf.compressed )
{
x3dom.debug.logError( "x3dom 1.8.2+ do not support compressed BinaryGeometries anymore" );
return;
}
var XHR_buffer = xmlhttp.response;
var geoNode = binGeo;
var attribTypeStr = geoNode._vf.coordType;
// assume same data type for all attributes (but might be wrong)
shape._webgl.coordType = x3dom.Utils.getVertexAttribType( attribTypeStr, gl );
shape._webgl.normalType = shape._webgl.coordType;
shape._webgl.texCoordType = shape._webgl.coordType;
shape._webgl.colorType = shape._webgl.coordType;
var attributes = x3dom.Utils.getArrayBufferView( attribTypeStr, XHR_buffer );
// calculate number of single data packages by including stride and type size
var dataLen = shape._coordStrideOffset[ 0 ] / x3dom.Utils.getDataTypeSize( attribTypeStr );
if ( dataLen )
{geoNode._mesh._numCoords = attributes.length / dataLen;}
if ( geoNode._vf.index.length == 0 )
{
for ( var i = 0; i < geoNode._vf.vertexCount.length; i++ )
{
if ( shape._webgl.primType[ i ] == gl.TRIANGLE_STRIP )
{geoNode._mesh._numFaces += geoNode._vf.vertexCount[ i ] - 2;}
else
{geoNode._mesh._numFaces += geoNode._vf.vertexCount[ i ] / 3;}
}
}
var buffer = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER, buffer );
gl.bufferData( gl.ARRAY_BUFFER, attributes, gl.STATIC_DRAW );
gl.vertexAttribPointer( sp.position, geoNode._mesh._numPosComponents,
shape._webgl.coordType, false,
shape._coordStrideOffset[ 0 ], shape._coordStrideOffset[ 1 ] );
gl.enableVertexAttribArray( sp.position );
if ( geoNode._vf.normal.length > 0 )
{
shape._webgl.buffers[ x3dom.BUFFER_IDX.NORMAL ] = buffer;
gl.bindBuffer( gl.ARRAY_BUFFER, buffer );
gl.bufferData( gl.ARRAY_BUFFER, attributes, gl.STATIC_DRAW );
gl.vertexAttribPointer( sp.normal, geoNode._mesh._numNormComponents,
shape._webgl.normalType, false,
shape._normalStrideOffset[ 0 ], shape._normalStrideOffset[ 1 ] );
gl.enableVertexAttribArray( sp.normal );
}
if ( geoNode._vf.texCoord.length > 0 )
{
shape._webgl.buffers[ x3dom.BUFFER_IDX.TEXCOORD ] = buffer;
gl.bindBuffer( gl.ARRAY_BUFFER, buffer );
gl.bufferData( gl.ARRAY_BUFFER, attributes, gl.STATIC_DRAW );
gl.vertexAttribPointer( sp.texcoord, geoNode._mesh._numTexComponents,
shape._webgl.texCoordType, false,
shape._texCoordStrideOffset[ 0 ], shape._texCoordStrideOffset[ 1 ] );
gl.enableVertexAttribArray( sp.texcoord );
}
if ( geoNode._vf.color.length > 0 )
{
shape._webgl.buffers[ x3dom.BUFFER_IDX.COLOR ] = buffer;
gl.bindBuffer( gl.ARRAY_BUFFER, buffer );
gl.bufferData( gl.ARRAY_BUFFER, attributes, gl.STATIC_DRAW );
gl.vertexAttribPointer( sp.color, geoNode._mesh._numColComponents,
shape._webgl.colorType, false,
shape._colorStrideOffset[ 0 ], shape._colorStrideOffset[ 1 ] );
gl.enableVertexAttribArray( sp.color );
}
attributes = null; // delete data block in CPU memory
if ( shape._webgl.internalDownloadCount == 0 )
{
shape._nameSpace.doc.needRender = true;
}
that.checkError( gl );
var t11 = Date.now() - t00;
x3dom.debug.logInfo( "XHR/ interleaved array load time: " + t11 + " ms" );
shape._webgl.buffers[ x3dom.BUFFER_IDX.POSITION ] = buffer;
};
}
// coord
if ( !binGeo._hasStrideOffset && binGeo._vf.coord.length > 0 )
{
var xmlhttp1 = new XMLHttpRequest();
xmlhttp1.open( "GET", shape._nameSpace.getURL( binGeo._vf.coord ), true );
xmlhttp1.responseType = "arraybuffer";
shape._nameSpace.doc.incrementDownloads();
//xmlhttp1.send(null);
x3dom.RequestManager.addRequest( xmlhttp1 );
xmlhttp1.onload = function ()
{
shape._nameSpace.doc.decrementDownloads();
shape._webgl.internalDownloadCount -= 1;
if ( xmlhttp1.status != 200 )
{
x3dom.debug.logError( "XHR1/ coord load failed with status: " + xmlhttp1.status );
return;
}
if ( !shape._webgl )
{return;}
if ( binGeo._vf.compressed )
{
x3dom.debug.logError( "x3dom 1.8.2+ do not support compressed BinaryGeometries anymore" );
return;
}
var XHR_buffer = xmlhttp1.response;
var geoNode = binGeo;
var i = 0;
var attribTypeStr = geoNode._vf.coordType;
shape._webgl.coordType = x3dom.Utils.getVertexAttribType( attribTypeStr, gl );
var vertices = x3dom.Utils.getArrayBufferView( attribTypeStr, XHR_buffer );
if ( createTriangleSoup )
{
shape._webgl.makeSeparateTris.pushBuffer( "coord", vertices );
return;
}
gl.bindAttribLocation( sp.program, 0, "position" );
var positionBuffer = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER, positionBuffer );
gl.bufferData( gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW );
gl.bindBuffer( gl.ARRAY_BUFFER, null );
geoNode._mesh._numCoords = vertices.length / geoNode._mesh._numPosComponents;
if ( geoNode._vf.index.length == 0 )
{
for ( i = 0; i < geoNode._vf.vertexCount.length; i++ )
{
if ( shape._webgl.primType[ i ] == gl.TRIANGLE_STRIP )
{geoNode._mesh._numFaces += geoNode._vf.vertexCount[ i ] - 2;}
else
{geoNode._mesh._numFaces += geoNode._vf.vertexCount[ i ] / 3;}
}
}
// Test reading Data
//x3dom.debug.logWarning("arraybuffer[0].vx="+vertices[0]);
if ( ( attribTypeStr == "Float32" ) &&
( shape._vf.bboxSize.x < 0 || shape._vf.bboxSize.y < 0 || shape._vf.bboxSize.z < 0 ) )
{
var min = new x3dom.fields.SFVec3f( vertices[ 0 ], vertices[ 1 ], vertices[ 2 ] );
var max = new x3dom.fields.SFVec3f( vertices[ 0 ], vertices[ 1 ], vertices[ 2 ] );
for ( i = 3; i < vertices.length; i += 3 )
{
if ( min.x > vertices[ i + 0 ] ) { min.x = vertices[ i + 0 ]; }
if ( min.y > vertices[ i + 1 ] ) { min.y = vertices[ i + 1 ]; }
if ( min.z > vertices[ i + 2 ] ) { min.z = vertices[ i + 2 ]; }
if ( max.x < vertices[ i + 0 ] ) { max.x = vertices[ i + 0 ]; }
if ( max.y < vertices[ i + 1 ] ) { max.y = vertices[ i + 1 ]; }
if ( max.z < vertices[ i + 2 ] ) { max.z = vertices[ i + 2 ]; }
}
// TODO; move to mesh for all cases?
shape._vf.bboxCenter.setValues( min.add( max ).multiply( 0.5 ) );
shape._vf.bboxSize.setValues( max.subtract( min ) );
}
vertices = null;
if ( shape._webgl.internalDownloadCount == 0 )
{
shape._nameSpace.doc.needRender = true;
}
that.checkError( gl );
var t11 = Date.now() - t00;
x3dom.debug.logInfo( "XHR1/ coord load time: " + t11 + " ms" );
shape._webgl.buffers[ x3dom.BUFFER_IDX.POSITION ] = positionBuffer;
};
}
// normal
if ( !binGeo._hasStrideOffset && binGeo._vf.normal.length > 0 )
{
var xmlhttp2 = new XMLHttpRequest();
xmlhttp2.open( "GET", shape._nameSpace.getURL( binGeo._vf.normal ), true );
xmlhttp2.responseType = "arraybuffer";
shape._nameSpace.doc.incrementDownloads();
//xmlhttp2.send(null);
x3dom.RequestManager.addRequest( xmlhttp2 );
xmlhttp2.onload = function ()
{
shape._nameSpace.doc.decrementDownloads();
shape._webgl.internalDownloadCount -= 1;
if ( xmlhttp2.status != 200 )
{
x3dom.debug.logError( "XHR2/ normal load failed with status: " + xmlhttp2.status );
return;
}
if ( !shape._webgl )
{return;}
if ( binGeo._vf.compressed )
{
x3dom.debug.logError( "x3dom 1.8.2+ do not support compressed BinaryGeometries anymore" );
return;
}
var XHR_buffer = xmlhttp2.response;
var attribTypeStr = binGeo._vf.normalType;
shape._webgl.normalType = x3dom.Utils.getVertexAttribType( attribTypeStr, gl );
var normals = x3dom.Utils.getArrayBufferView( attribTypeStr, XHR_buffer );
if ( createTriangleSoup )
{
shape._webgl.makeSeparateTris.pushBuffer( "normal", normals );
return;
}
var normalBuffer = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER, normalBuffer );
gl.bufferData( gl.ARRAY_BUFFER, normals, gl.STATIC_DRAW );
gl.bindBuffer( gl.ARRAY_BUFFER, null );
// Test reading Data
//x3dom.debug.logWarning("arraybuffer[0].nx="+normals[0]);
normals = null;
if ( shape._webgl.internalDownloadCount == 0 )
{
shape._nameSpace.doc.needRender = true;
}
that.checkError( gl );
var t11 = Date.now() - t00;
x3dom.debug.logInfo( "XHR2/ normal load time: " + t11 + " ms" );
shape._webgl.buffers[ x3dom.BUFFER_IDX.NORMAL ] = normalBuffer;
};
}
// texCoord
if ( !binGeo._hasStrideOffset && binGeo._vf.texCoord.length > 0 )
{
var xmlhttp3 = new XMLHttpRequest();
xmlhttp3.open( "GET", shape._nameSpace.getURL( binGeo._vf.texCoord ), true );
xmlhttp3.responseType = "arraybuffer";
shape._nameSpace.doc.incrementDownloads();
//xmlhttp3.send(null);
x3dom.RequestManager.addRequest( xmlhttp3 );
xmlhttp3.onload = function ()
{
var i,
j,
tmp;
shape._nameSpace.doc.decrementDownloads();
shape._webgl.internalDownloadCount -= 1;
if ( xmlhttp3.status != 200 )
{
x3dom.debug.logError( "XHR3/ texcoord load failed with status: " + xmlhttp3.status );
return;
}
if ( !shape._webgl )
{return;}
if ( binGeo._vf.compressed )
{
x3dom.debug.logError( "x3dom 1.8.2+ do not support compressed BinaryGeometries anymore" );
return;
}
var XHR_buffer = xmlhttp3.response;
var attribTypeStr = binGeo._vf.texCoordType;
shape._webgl.texCoordType = x3dom.Utils.getVertexAttribType( attribTypeStr, gl );
var texCoords = x3dom.Utils.getArrayBufferView( attribTypeStr, XHR_buffer );
if ( createTriangleSoup )
{
shape._webgl.makeSeparateTris.pushBuffer( "texCoord", texCoords );
return;
}
//if IDs are given in texture coordinates, interpret texcoords as ID buffer
if ( binGeo._vf[ "idsPerVertex" ] )
{
var idBuffer = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER, idBuffer );
//Create a buffer for the ids with half size of the texccoord buffer
var ids = x3dom.Utils.getArrayBufferView( "Float32", texCoords.length / 2 );
//swap x and y, in order to interpret tex coords as FLOAT later on
for ( i = 0, j = 0; i < texCoords.length; i += 2, j++ )
{
ids[ j ] = texCoords[ i + 1 ] * 65536 + texCoords[ i ];
}
gl.bufferData( gl.ARRAY_BUFFER, ids, gl.STATIC_DRAW );
gl.bindBuffer( gl.ARRAY_BUFFER, null );
shape._webgl.buffers[ x3dom.BUFFER_IDX.ID ] = idBuffer;
}
else
{
var texcBuffer = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER, texcBuffer );
gl.bufferData( gl.ARRAY_BUFFER, texCoords, gl.STATIC_DRAW );
gl.bindBuffer( gl.ARRAY_BUFFER, null );
shape._webgl.buffers[ x3dom.BUFFER_IDX.TEXCOORD ] = texcBuffer;
}
// Test reading Data
//x3dom.debug.logWarning("arraybuffer[0].tx="+texCoords[0]);
texCoords = null;
if ( shape._webgl.internalDownloadCount == 0 )
{
shape._nameSpace.doc.needRender = true;
}
that.checkError( gl );
var t11 = Date.now() - t00;
x3dom.debug.logInfo( "XHR3/ texCoord load time: " + t11 + " ms" );
};
}
// color
if ( !binGeo._hasStrideOffset && binGeo._vf.color.length > 0 )
{
var xmlhttp4 = new XMLHttpRequest();
xmlhttp4.open( "GET", shape._nameSpace.getURL( binGeo._vf.color ), true );
xmlhttp4.responseType = "arraybuffer";
shape._nameSpace.doc.incrementDownloads();
//xmlhttp4.send(null);
x3dom.RequestManager.addRequest( xmlhttp4 );
xmlhttp4.onload = function ()
{
shape._nameSpace.doc.decrementDownloads();
shape._webgl.internalDownloadCount -= 1;
if ( xmlhttp4.status != 200 )
{
x3dom.debug.logError( "XHR4/ color load failed with status: " + xmlhttp4.status );
return;
}
if ( !shape._webgl )
{return;}
if ( binGeo._vf.compressed )
{
x3dom.debug.logError( "x3dom 1.8.2+ do not support compressed BinaryGeometries anymore" );
return;
}
var XHR_buffer = xmlhttp4.response;
var attribTypeStr = binGeo._vf.colorType;
shape._webgl.colorType = x3dom.Utils.getVertexAttribType( attribTypeStr, gl );
var colors = x3dom.Utils.getArrayBufferView( attribTypeStr, XHR_buffer );
if ( createTriangleSoup )
{
shape._webgl.makeSeparateTris.pushBuffer( "color", colors );
return;
}
var colorBuffer = gl.createBuffer();
gl.bindBuffer( gl.ARRAY_BUFFER, colorBuffer );
gl.bufferData( gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW );
gl.bindBuffer( gl.ARRAY_BUFFER, null );
// Test reading Data
//x3dom.debug.logWarning("arraybuffer[0].cx="+colors[0]);
colors = null;
if ( shape._webgl.internalDownloadCount == 0 )
{
shape._nameSpace.doc.needRender = true;
}
that.checkError( gl );
var t11 = Date.now() - t00;
x3dom.debug.logInfo( "XHR4/ color load time: " + t11 + " ms" );
shape._webgl.buffers[ x3dom.BUFFER_IDX.COLOR ] = colorBuffer;
};
}
// tangents
if ( !binGeo._hasStrideOffset && binGeo._vf.tangent.length > 0 )
{
var xmlhttp5 = new XMLHttpRequest();
xmlhttp5.open( "GET", shape._nameSpace.getURL( binGeo._vf.normal ), true );
xmlhttp5.responseType = "arraybuffer";
shape._nameSpace.doc.incrementDownloads();
//xmlhttp2.send(null);
x3dom.RequestManager.addRequest( xmlhttp5 );
xmlhttp5.onload = function ()
{
shape._nameSpace.doc.decrementDownloads();
shape._webgl.internalDownloadCount -= 1;
if ( xmlhttp5.status != 200 )
{
x3dom.debug.logError( "XHR2/ normal load failed with status: " + xmlhttp5.status );
return;
}
if ( !shape._webgl )
{return;}
if ( binGeo._vf.compressed )
{
x3dom.debug.logError( "x3dom 1.8.2+ do not support compressed BinaryGeometries anymore" );
return;
}
var XHR_buffer = xmlhttp5.response;
var attribTypeStr = binGeo._vf.tangentTyp
View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

View raw

(Sorry about that, but we can’t show files that are this big right now.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment