//////////////////////////////////////////////////////////////////////////////// // // (C) 2012--today, Alexander Grahn // // 3Dmenu.js // // version 20140923 // //////////////////////////////////////////////////////////////////////////////// // // 3D JavaScript used by media9.sty // // Extended functionality of the (right click) context menu of 3D annotations. // // 1.) Adds the following items to the 3D context menu: // // * `Generate Default View' // // Finds good default camera settings, returned as options for use with // the \includemedia command. // // * `Get Current View' // // Determines camera, cross section and part settings of the current view, // returned as `VIEW' section that can be copied into a views file of // additional views. The views file is inserted using the `3Dviews' option // of \includemedia. // // * `Cross Section' // // Toggle switch to add or remove a cross section into or from the current // view. The cross section can be moved in the x, y, z directions using x, // y, z and X, Y, Z keys on the keyboard, be tilted against and spun // around the upright Z axis using the Up/Down and Left/Right arrow keys // and caled using the s and S keys. // // 2.) Enables manipulation of position and orientation of indiviual parts and // groups of parts in the 3D scene. Parts which have been selected with the // mouse can be scaled moved around and rotated like the cross section as // described above. To spin the parts around their local up-axis, keep // Control key pressed while using the Up/Down and Left/Right arrow keys. // // This work may be distributed and/or modified under the // conditions of the LaTeX Project Public License. // // The latest version of this license is in // http://mirrors.ctan.org/macros/latex/base/lppl.txt // // This work has the LPPL maintenance status `maintained'. // // The Current Maintainer of this work is A. Grahn. // // The code borrows heavily from Bernd Gaertners `Miniball' software, // originally written in C++, for computing the smallest enclosing ball of a // set of points; see: http://www.inf.ethz.ch/personal/gaertner/miniball.html // //////////////////////////////////////////////////////////////////////////////// //host.console.show(); //constructor for doubly linked list function List(){ this.first_node=null; this.last_node=new Node(undefined); } List.prototype.push_back=function(x){ var new_node=new Node(x); if(this.first_node==null){ this.first_node=new_node; new_node.prev=null; }else{ new_node.prev=this.last_node.prev; new_node.prev.next=new_node; } new_node.next=this.last_node; this.last_node.prev=new_node; }; List.prototype.move_to_front=function(it){ var node=it.get(); if(node.next!=null && node.prev!=null){ node.next.prev=node.prev; node.prev.next=node.next; node.prev=null; node.next=this.first_node; this.first_node.prev=node; this.first_node=node; } }; List.prototype.begin=function(){ var i=new Iterator(); i.target=this.first_node; return(i); }; List.prototype.end=function(){ var i=new Iterator(); i.target=this.last_node; return(i); }; function Iterator(it){ if( it!=undefined ){ this.target=it.target; }else { this.target=null; } } Iterator.prototype.set=function(it){this.target=it.target;}; Iterator.prototype.get=function(){return(this.target);}; Iterator.prototype.deref=function(){return(this.target.data);}; Iterator.prototype.incr=function(){ if(this.target.next!=null) this.target=this.target.next; }; //constructor for node objects that populate the linked list function Node(x){ this.prev=null; this.next=null; this.data=x; } function sqr(r){return(r*r);}//helper function //Miniball algorithm by B. Gaertner function Basis(){ this.m=0; this.q0=new Array(3); this.z=new Array(4); this.f=new Array(4); this.v=new Array(new Array(3), new Array(3), new Array(3), new Array(3)); this.a=new Array(new Array(3), new Array(3), new Array(3), new Array(3)); this.c=new Array(new Array(3), new Array(3), new Array(3), new Array(3)); this.sqr_r=new Array(4); this.current_c=this.c[0]; this.current_sqr_r=0; this.reset(); } Basis.prototype.center=function(){return(this.current_c);}; Basis.prototype.size=function(){return(this.m);}; Basis.prototype.pop=function(){--this.m;}; Basis.prototype.excess=function(p){ var e=-this.current_sqr_r; for(var k=0;k<3;++k){ e+=sqr(p[k]-this.current_c[k]); } return(e); }; Basis.prototype.reset=function(){ this.m=0; for(var j=0;j<3;++j){ this.c[0][j]=0; } this.current_c=this.c[0]; this.current_sqr_r=-1; }; Basis.prototype.push=function(p){ var i, j; var eps=1e-32; if(this.m==0){ for(i=0;i<3;++i){ this.q0[i]=p[i]; } for(i=0;i<3;++i){ this.c[0][i]=this.q0[i]; } this.sqr_r[0]=0; }else { for(i=0;i<3;++i){ this.v[this.m][i]=p[i]-this.q0[i]; } for(i=1;i 0){ if(this.B.push(j.deref())){ this.mtf_mb(j); this.B.pop(); if(this.support_end.get()==j.get()) this.support_end.incr(); this.L.move_to_front(j); } } } }; Miniball.prototype.check_in=function(b){ this.L.push_back(b); }; Miniball.prototype.build=function(){ this.B.reset(); this.support_end.set(this.L.begin()); this.mtf_mb(this.L.end()); }; Miniball.prototype.center=function(){ return(this.B.center()); }; Miniball.prototype.radius=function(){ return(Math.sqrt(this.B.current_sqr_r)); }; //functions called by menu items function calc3Dopts () { //create Miniball object var mb=new Miniball(); //auxiliary vector var corner=new Vector3(); //iterate over all visible mesh nodes in the scene for(i=0;i new camera target var coo=new Vector3(); coo.set((mb.center())[0], (mb.center())[1], (mb.center())[2]); if(coo.length) res+=host.util.printf('\n3Dcoo=%s %s %s,', coo.x, coo.y, coo.z); //radius of orbit if(camera.projectionType==camera.TYPE_PERSPECTIVE){ var roo=mb.radius()/ Math.sin(aac * Math.PI/ 360.); }else{ //orthographic projection var roo=mb.radius(); } res+=host.util.printf('\n3Droo=%s,', roo); //update camera settings in the viewer var currol=camera.roll; camera.targetPosition.set(coo); camera.position.set(coo.add(c2c.scale(roo))); camera.roll=currol; //determine background colour rgb=scene.background.getColor(); if(!(rgb.r==1 && rgb.g==1 && rgb.b==1)) res+=host.util.printf('\n3Dbg=%s %s %s,', rgb.r, rgb.g, rgb.b); //determine lighting scheme switch(scene.lightScheme){ case scene.LIGHT_MODE_FILE: curlights='Artwork';break; case scene.LIGHT_MODE_NONE: curlights='None';break; case scene.LIGHT_MODE_WHITE: curlights='White';break; case scene.LIGHT_MODE_DAY: curlights='Day';break; case scene.LIGHT_MODE_NIGHT: curlights='Night';break; case scene.LIGHT_MODE_BRIGHT: curlights='Hard';break; case scene.LIGHT_MODE_RGB: curlights='Primary';break; case scene.LIGHT_MODE_BLUE: curlights='Blue';break; case scene.LIGHT_MODE_RED: curlights='Red';break; case scene.LIGHT_MODE_CUBE: curlights='Cube';break; case scene.LIGHT_MODE_CAD: curlights='CAD';break; case scene.LIGHT_MODE_HEADLAMP: curlights='Headlamp';break; } if(curlights!='Artwork') res+=host.util.printf('\n3Dlights=%s,', curlights); //determine global render mode switch(scene.renderMode){ case scene.RENDER_MODE_BOUNDING_BOX: currender='BoundingBox';break; case scene.RENDER_MODE_TRANSPARENT_BOUNDING_BOX: currender='TransparentBoundingBox';break; case scene.RENDER_MODE_TRANSPARENT_BOUNDING_BOX_OUTLINE: currender='TransparentBoundingBoxOutline';break; case scene.RENDER_MODE_VERTICES: currender='Vertices';break; case scene.RENDER_MODE_SHADED_VERTICES: currender='ShadedVertices';break; case scene.RENDER_MODE_WIREFRAME: currender='Wireframe';break; case scene.RENDER_MODE_SHADED_WIREFRAME: currender='ShadedWireframe';break; case scene.RENDER_MODE_SOLID: currender='Solid';break; case scene.RENDER_MODE_TRANSPARENT: currender='Transparent';break; case scene.RENDER_MODE_SOLID_WIREFRAME: currender='SolidWireframe';break; case scene.RENDER_MODE_TRANSPARENT_WIREFRAME: currender='TransparentWireframe';break; case scene.RENDER_MODE_ILLUSTRATION: currender='Illustration';break; case scene.RENDER_MODE_SOLID_OUTLINE: currender='SolidOutline';break; case scene.RENDER_MODE_SHADED_ILLUSTRATION: currender='ShadedIllustration';break; case scene.RENDER_MODE_HIDDEN_WIREFRAME: currender='HiddenWireframe';break; } if(currender!='Solid') res+=host.util.printf('\n3Drender=%s,', currender); //write result string to the console host.console.show(); // host.console.clear(); host.console.println('%%\n%% Copy and paste the following text to the\n'+ '%% option list of \\includemedia!\n%%' + res + '\n'); } function get3Dview () { var camera=scene.cameras.getByIndex(0); var coo=camera.targetPosition; var c2c=camera.position.subtract(coo); var roo=c2c.length; c2c.normalize(); var res='VIEW%=insert optional name here\n'; if(!(coo.x==0 && coo.y==0 && coo.z==0)) res+=host.util.printf(' COO=%s %s %s\n', coo.x, coo.y, coo.z); if(!(c2c.x==0 && c2c.y==-1 && c2c.z==0)) res+=host.util.printf(' C2C=%s %s %s\n', c2c.x, c2c.y, c2c.z); if(roo > 1e-9) res+=host.util.printf(' ROO=%s\n', roo); var roll = camera.roll*180/Math.PI; if(host.util.printf('%.4f', roll)!=0) res+=host.util.printf(' ROLL=%s\n', roll); if(camera.projectionType==camera.TYPE_PERSPECTIVE){ var aac=camera.fov * 180/Math.PI; if(host.util.printf('%.4f', aac)!=30) res+=host.util.printf(' AAC=%s\n', aac); }else{ if(host.util.printf('%.4f', camera.viewPlaneSize)!=1) res+=host.util.printf(' ORTHO=%s\n', 1./camera.viewPlaneSize); } rgb=scene.background.getColor(); if(!(rgb.r==1 && rgb.g==1 && rgb.b==1)) res+=host.util.printf(' BGCOLOR=%s %s %s\n', rgb.r, rgb.g, rgb.b); switch(scene.lightScheme){ case scene.LIGHT_MODE_FILE: curlights='Artwork';break; case scene.LIGHT_MODE_NONE: curlights='None';break; case scene.LIGHT_MODE_WHITE: curlights='White';break; case scene.LIGHT_MODE_DAY: curlights='Day';break; case scene.LIGHT_MODE_NIGHT: curlights='Night';break; case scene.LIGHT_MODE_BRIGHT: curlights='Hard';break; case scene.LIGHT_MODE_RGB: curlights='Primary';break; case scene.LIGHT_MODE_BLUE: curlights='Blue';break; case scene.LIGHT_MODE_RED: curlights='Red';break; case scene.LIGHT_MODE_CUBE: curlights='Cube';break; case scene.LIGHT_MODE_CAD: curlights='CAD';break; case scene.LIGHT_MODE_HEADLAMP: curlights='Headlamp';break; } if(curlights!='Artwork') res+=' LIGHTS='+curlights+'\n'; switch(scene.renderMode){ case scene.RENDER_MODE_BOUNDING_BOX: defaultrender='BoundingBox';break; case scene.RENDER_MODE_TRANSPARENT_BOUNDING_BOX: defaultrender='TransparentBoundingBox';break; case scene.RENDER_MODE_TRANSPARENT_BOUNDING_BOX_OUTLINE: defaultrender='TransparentBoundingBoxOutline';break; case scene.RENDER_MODE_VERTICES: defaultrender='Vertices';break; case scene.RENDER_MODE_SHADED_VERTICES: defaultrender='ShadedVertices';break; case scene.RENDER_MODE_WIREFRAME: defaultrender='Wireframe';break; case scene.RENDER_MODE_SHADED_WIREFRAME: defaultrender='ShadedWireframe';break; case scene.RENDER_MODE_SOLID: defaultrender='Solid';break; case scene.RENDER_MODE_TRANSPARENT: defaultrender='Transparent';break; case scene.RENDER_MODE_SOLID_WIREFRAME: defaultrender='SolidWireframe';break; case scene.RENDER_MODE_TRANSPARENT_WIREFRAME: defaultrender='TransparentWireframe';break; case scene.RENDER_MODE_ILLUSTRATION: defaultrender='Illustration';break; case scene.RENDER_MODE_SOLID_OUTLINE: defaultrender='SolidOutline';break; case scene.RENDER_MODE_SHADED_ILLUSTRATION: defaultrender='ShadedIllustration';break; case scene.RENDER_MODE_HIDDEN_WIREFRAME: defaultrender='HiddenWireframe';break; } if(defaultrender!='Solid') res+=' RENDERMODE='+defaultrender+'\n'; //detect existing Clipping Plane (3D Cross Section) var clip=null; if( clip=scene.nodes.getByName('$$$$$$')|| clip=scene.nodes.getByName('Clipping Plane') ); for(var i=0;i0) var ndUserName=nd.name.substr(0,end); else var ndUserName=nd.name; respart=' PART='+ndUserName+'\n'; respart+=' UTF16NAME='+ndUTFName+'\n'; defaultvals=true; if(!nd.visible){ respart+=' VISIBLE=false\n'; defaultvals=false; } if(nd.opacity<1.0){ respart+=' OPACITY='+nd.opacity+'\n'; defaultvals=false; } if(nd.constructor.name=='Mesh'){ currender=defaultrender; switch(nd.renderMode){ case scene.RENDER_MODE_BOUNDING_BOX: currender='BoundingBox';break; case scene.RENDER_MODE_TRANSPARENT_BOUNDING_BOX: currender='TransparentBoundingBox';break; case scene.RENDER_MODE_TRANSPARENT_BOUNDING_BOX_OUTLINE: currender='TransparentBoundingBoxOutline';break; case scene.RENDER_MODE_VERTICES: currender='Vertices';break; case scene.RENDER_MODE_SHADED_VERTICES: currender='ShadedVertices';break; case scene.RENDER_MODE_WIREFRAME: currender='Wireframe';break; case scene.RENDER_MODE_SHADED_WIREFRAME: currender='ShadedWireframe';break; case scene.RENDER_MODE_SOLID: currender='Solid';break; case scene.RENDER_MODE_TRANSPARENT: currender='Transparent';break; case scene.RENDER_MODE_SOLID_WIREFRAME: currender='SolidWireframe';break; case scene.RENDER_MODE_TRANSPARENT_WIREFRAME: currender='TransparentWireframe';break; case scene.RENDER_MODE_ILLUSTRATION: currender='Illustration';break; case scene.RENDER_MODE_SOLID_OUTLINE: currender='SolidOutline';break; case scene.RENDER_MODE_SHADED_ILLUSTRATION: currender='ShadedIllustration';break; case scene.RENDER_MODE_HIDDEN_WIREFRAME: currender='HiddenWireframe';break; //case scene.RENDER_MODE_DEFAULT: // currender='Default';break; } if(currender!=defaultrender){ respart+=' RENDERMODE='+currender+'\n'; defaultvals=false; } } if(origtrans[nd.name]&&!nd.transform.isEqual(origtrans[nd.name])){ var lvec=nd.transform.transformDirection(new Vector3(1,0,0)); var uvec=nd.transform.transformDirection(new Vector3(0,1,0)); var vvec=nd.transform.transformDirection(new Vector3(0,0,1)); respart+=' TRANSFORM=' +lvec.x+' '+lvec.y+' '+lvec.z+' ' +uvec.x+' '+uvec.y+' '+uvec.z+' ' +vvec.x+' '+vvec.y+' '+vvec.z+' ' +nd.transform.translation.x+' ' +nd.transform.translation.y+' ' +nd.transform.translation.z+'\n'; defaultvals=false; } respart+=' END\n'; if(!defaultvals) res+=respart; } if(clip){ var centre=clip.transform.translation; var normal=clip.transform.transformDirection(new Vector3(0,0,1)); res+=' CROSSSECT\n'; if(!(centre.x==0 && centre.y==0 && centre.z==0)) res+=host.util.printf( ' CENTER=%s %s %s\n', centre.x, centre.y, centre.z); if(!(normal.x==1 && normal.y==0 && normal.z==0)) res+=host.util.printf( ' NORMAL=%s %s %s\n', normal.x, normal.y, normal.z); res+=host.util.printf( ' VISIBLE=%s\n', clip.visible); res+=host.util.printf( ' PLANECOLOR=%s %s %s\n', clip.material.emissiveColor.r, clip.material.emissiveColor.g, clip.material.emissiveColor.b); res+=host.util.printf( ' OPACITY=%s\n', clip.opacity); res+=host.util.printf( ' INTERSECTIONCOLOR=%s %s %s\n', clip.wireframeColor.r, clip.wireframeColor.g, clip.wireframeColor.b); res+=' END\n'; // for(var propt in clip){ // console.println(propt+':'+clip[propt]); // } } res+='END\n'; host.console.show(); // host.console.clear(); host.console.println('%%\n%% Add the following VIEW section to a file of\n'+ '%% predefined views (See option "3Dviews"!).\n%%\n' + '%% The view may be given a name after VIEW=...\n' + '%% (Remove \'%\' in front of \'=\'.)\n%%'); host.console.println(res + '\n'); } //add items to 3D context menu runtime.addCustomMenuItem("dfltview", "Generate Default View", "default", 0); runtime.addCustomMenuItem("currview", "Get Current View", "default", 0); runtime.addCustomMenuItem("csection", "Cross Section", "checked", 0); //menu event handlers menuEventHandler = new MenuEventHandler(); menuEventHandler.onEvent = function(e) { switch(e.menuItemName){ case "dfltview": calc3Dopts(); break; case "currview": get3Dview(); break; case "csection": addremoveClipPlane(e.menuItemChecked); break; } }; runtime.addEventHandler(menuEventHandler); //global variable taking reference to currently selected node; var target=null; selectionEventHandler=new SelectionEventHandler(); selectionEventHandler.onEvent=function(e){ if(e.selected&&e.node.name!=''){ target=e.node; }else{ target=null; } } runtime.addEventHandler(selectionEventHandler); cameraEventHandler=new CameraEventHandler(); cameraEventHandler.onEvent=function(e){ var clip=null; runtime.removeCustomMenuItem("csection"); runtime.addCustomMenuItem("csection", "Cross Section", "checked", 0); if(clip=scene.nodes.getByName('$$$$$$')|| //predefined scene.nodes.getByName('Clipping Plane')){ //added via context menu runtime.removeCustomMenuItem("csection"); runtime.addCustomMenuItem("csection", "Cross Section", "checked", 1); } if(clip){//plane in predefined views must be rotated by 90 deg around normal clip.transform.rotateAboutLineInPlace( Math.PI/2,clip.transform.translation, clip.transform.transformDirection(new Vector3(0,0,1)) ); } for(var i=0; i