diff --git a/icons.css b/icons.css index 008faebc..ae40ee00 100644 --- a/icons.css +++ b/icons.css @@ -286,4 +286,5 @@ .icon-button-siege:before {content:'🏰'; padding-right: .4em;} .icon-button-ambush:before {content:'🌳'; padding-right: .4em;} .icon-button-landing:before {content:'⚓'; padding-right: .4em;} -.icon-button-air:before {content:'💨'; padding-right: .4em;} \ No newline at end of file +.icon-button-air:before {content:'💨'; padding-right: .4em;} +.icon-button-screenshot:before {content:'🖥️'; padding-right: .4em;} diff --git a/index.html b/index.html index 602c922c..50d83aab 100644 --- a/index.html +++ b/index.html @@ -3358,7 +3358,8 @@
- + +
diff --git a/libs/objexporter.min.js b/libs/objexporter.min.js new file mode 100644 index 00000000..fe5b5289 --- /dev/null +++ b/libs/objexporter.min.js @@ -0,0 +1,5 @@ +THREE.OBJExporter=function(){}; +THREE.OBJExporter.prototype={constructor:THREE.OBJExporter,parse:function(A){var f="",n=0,w=0,x=0,g=new THREE.Vector3,p=new THREE.Vector3,u=new THREE.Vector2,a,b,y,c,k,v=[];A.traverse(function(e){if(e instanceof THREE.Mesh){var r=0,t=0,d=0,h=e.geometry,z=new THREE.Matrix3;h instanceof THREE.Geometry&&(h=(new THREE.BufferGeometry).setFromObject(e));if(h instanceof THREE.BufferGeometry){var q=h.getAttribute("position"),l=h.getAttribute("normal"),m=h.getAttribute("uv");h=h.getIndex();f+="o "+e.name+ +"\n";e.material&&e.material.name&&(f+="usemtl "+e.material.name+"\n");if(void 0!==q)for(a=0,c=q.count;ak;k++)b=h.getX(a+k)+1,v[k]=n+b+(l||m?"/"+(m?w+b:"")+(l?"/"+(x+b):""):"");f+="f "+v.join(" ")+"\n"}else for(a=0,c=q.count;ak;k++)b=a+k+1,v[k]=n+b+(l||m?"/"+(m?w+b:"")+(l?"/"+(x+b):""):"");f+="f "+v.join(" ")+"\n"}}else console.warn("THREE.OBJExporter.parseMesh(): geometry type unsupported",h);n+=r;w+=d;x+=t}if(e instanceof THREE.Line){r=0;d=e.geometry;t=e.type;d instanceof THREE.Geometry&&(d=(new THREE.BufferGeometry).setFromObject(e));if(d instanceof +THREE.BufferGeometry){d=d.getAttribute("position");f+="o "+e.name+"\n";if(void 0!==d)for(a=0,c=d.count;a window.URL.revokeObjectURL(URL), 5000); } +const saveOBJ = async function() { + downloadFile(await getOBJ(), getFileName() + ".obj", "text/plain;charset=UTF-8"); +} + // start 3d view and heightmap edit preview async function newMesh(canvas) { const loaded = await loadTHREE(); @@ -289,6 +294,13 @@ async function updateGlobeTexure(addMesh) { img2.src = await getMapURL("mesh", "globe");; } +async function getOBJ() { + objexporter = await OBJExporter(); + + const data = await objexporter.parse(mesh); + return data; +} + function addGlobe3dMesh() { geometry = new THREE.SphereBufferGeometry(1, 64, 64); mesh = new THREE.Mesh(geometry, material); @@ -332,6 +344,18 @@ function OrbitControls(camera, domElement) { }); } -return {create, redraw, update, stop, options, setScale, setLightness, setSun, setRotation, toggleSky, setResolution, setColors, saveScreenshot}; +function OBJExporter() { + if (THREE.OBJExporter) return new THREE.OBJExporter(); + + return new Promise(resolve => { + const script = document.createElement('script'); + script.src = "libs/objexporter.min.js" + document.head.append(script); + script.onload = () => resolve(new THREE.OBJExporter()); + script.onerror = () => resolve(false); + }); +} + +return {create, redraw, update, stop, options, setScale, setLightness, setSun, setRotation, toggleSky, setResolution, setColors, saveScreenshot, saveOBJ}; }))); diff --git a/modules/ui/options.js b/modules/ui/options.js index 45488121..8d7bb481 100644 --- a/modules/ui/options.js +++ b/modules/ui/options.js @@ -707,6 +707,7 @@ function toggle3dOptions() { document.getElementById("options3dUpdate").addEventListener("click", ThreeD.update); document.getElementById("options3dSave").addEventListener("click", ThreeD.saveScreenshot); + document.getElementById("options3dOBJSave").addEventListener("click", ThreeD.saveOBJ); document.getElementById("options3dScaleRange").addEventListener("input", changeHeightScale); document.getElementById("options3dScaleNumber").addEventListener("change", changeHeightScale);