3D Scene system upgrade. (#956)

* 3d view system upgrade.

* version fix

* Versioning fixed.

* Subdivision Added

* Subdivision added. Removed toggle wireframe as an option.
Reverted to previous rendering method.

* Update obj export because new threejs version.

* Clean up of unrequired code.

* Multiple fixes to 3D view upgrade PR.

* Remove unused code.(for3DRender)
This commit is contained in:
Efruz Yıldırır 2023-08-05 21:54:13 +03:00 committed by GitHub
parent c398bc64d6
commit 3d8aa7c3ca
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 348 additions and 1042 deletions

View file

@ -5666,6 +5666,19 @@
<input id="options3dLightnessNumber" type="number" min="0" max="500" style="width: 4em" />
</div>
<div data-tip="Set mesh texture resolution">
<div>Texture resolution:</div>
<select id="options3dMeshSkinResolution" style="width: 10em">
<option value="512">512x512px</option>
<option value="1024">1024x1024px</option>
<option value="2048">2048x2048px</option>
<option value="4096">4096x4096px</option>
<option value="8192">8192x8192px</option>
</select>
</div>
<div data-tip="Set sun position (x, y, z) to define shadows">
<div>Sun position:</div>
<input id="options3dSunX" type="number" min="-2500" max="2500" step="100" style="width: 4.7em" />
@ -5673,6 +5686,8 @@
<input id="options3dSunZ" type="number" min="-1500" max="1500" step="100" style="width: 4.7em" />
</div>
<div data-tip="Toggle 3d labels" style="margin: 0.6em 0 0.3em -0.2em">
<input id="options3dMeshLabels3d" class="checkbox" type="checkbox" />
<label for="options3dMeshLabels3d" class="checkbox-label"><i>Show 3D labels</i></label>
@ -5683,6 +5698,20 @@
<label for="options3dMeshSkyMode" class="checkbox-label"><i>Show sky and extend water</i></label>
</div>
<div data-tip="Toggle 3d subdivision. Increases the polygon count. Opening this will take some time. WARNING: Can smooth the sharp points in progress." style="margin: 0.6em 0 0.3em -0.2em">
<input id="options3dSubdivide" class="checkbox" type="checkbox" />
<label for="options3dSubdivide" class="checkbox-label"><i>Smooth Geometry.</i></label>
</div>
<div data-tip="Set Sun Color" id="options3dSunColorSection">
<span>Sun Color:</span
><input
id="options3dSunColor"
type="color"
style="width: 4.4em; height: 1em; border: 0; padding: 0; margin: 0 0.2em"
/>
</div>
<div data-tip="Set sky and water color" id="options3dColorSection" style="display: none">
<span>Sky:</span
><input
@ -7899,7 +7928,7 @@
<script src="modules/ui/stylePresets.js?v=1.89.11"></script>
<script src="modules/ui/general.js?v=1.87.03"></script>
<script src="modules/ui/options.js?v=1.89.19"></script>
<script src="modules/ui/options.js?v=1.89.36"></script>
<script src="main.js?v=1.89.32"></script>
<script defer src="modules/relief-icons.js"></script>
@ -7935,7 +7964,7 @@
<script defer src="modules/ui/battle-screen.js"></script>
<script defer src="modules/ui/emblems-editor.js?v=1.89.21"></script>
<script defer src="modules/ui/markers-editor.js"></script>
<script defer src="modules/ui/3d.js"></script>
<script defer src="modules/ui/3d.js?v=1.89.36"></script>
<script defer src="modules/ui/submap.js"></script>
<script defer src="modules/ui/hotkeys.js?v=1.88.00"></script>
<script defer src="modules/coa-renderer.js?v=1.87.08"></script>
@ -7945,7 +7974,7 @@
<script defer src="modules/io/save.js?v=1.89.29"></script>
<script defer src="modules/io/load.js?v=1.89.30"></script>
<script defer src="modules/io/cloud.js"></script>
<script defer src="modules/io/export.js?v=1.89.17"></script>
<script defer src="modules/io/export.js?v=1.89.36"></script>
<script defer src="modules/io/formats.js"></script>
<!-- Web Components -->

132
libs/loopsubdivison.min.js vendored Normal file
View file

@ -0,0 +1,132 @@
/**
* @description Loop Subdivision Surface
* @about Smooth subdivision surface modifier for use with three.js BufferGeometry.
* @author Stephens Nunnally <@stevinz>
* @license MIT - Copyright (c) 2022 Stephens Nunnally
* @source https://github.com/stevinz/three-subdivide
*/
/////////////////////////////////////////////////////////////////////////////////////
//
// Functions
// modify Applies Loop subdivision to BufferGeometry, returns new BufferGeometry
// edgeSplit Splits all triangles at edges shared by coplanar triangles
// flat One iteration of Loop subdivision, without point averaging
// smooth One iteration of Loop subdivision, with point averaging
//
// Info
// This modifier uses the Loop (Charles Loop, 1987) subdivision surface algorithm to smooth
// modern three.js BufferGeometry.
//
// At one point, three.js included a subdivision surface modifier in the extended examples (see bottom
// of file for links), it was removed in r125. The modifier was originally based on the Catmull-Clark
// algorithm, which works best for geometry with convex coplanar n-gon faces. In three.js r60 the modifier
// was changed to utilize the Loop algorithm. The Loop algorithm was designed to work better with triangle
// based meshes.
//
// The Loop algorithm, however, doesn't always provide uniform results as the vertices are
// skewed toward the most used vertex positions. A triangle based box (e.g. BoxGeometry for example) will
// tend to favor the corners. To alleviate this issue, this implementation includes an initial pass to split
// coplanar faces at their shared edges. It starts by splitting along the longest shared edge first, and then
// from that midpoint it splits to any remaining coplanar shared edges.
//
// Also by default, this implementation inserts new uv coordinates, but does not average them using the Loop
// algorithm. In some cases (often in flat geometries) this will produce undesired results, a
// noticeable tearing will occur. In such cases, try passing 'uvSmooth' as true to enable uv averaging.
//
// Note(s)
// - This modifier returns a new BufferGeometry instance, it does not dispose() of the old geometry.
//
// - This modifier returns a NonIndexed geometry. An Indexed geometry can be created by using the
// BufferGeometryUtils.mergeVertices() function, see:
// https://threejs.org/docs/?q=buffer#examples/en/utils/BufferGeometryUtils.mergeVertices
//
// - This modifier works best with geometry whose triangles share edges AND edge vertices. See diagram below.
//
// OKAY NOT OKAY
// O O
// /|\ / \
// / | \ / \
// / | \ / \
// O---O---O O---O---O
// \ | / \ | /
// \ | / \ | /
// \|/ \|/
// O O
//
// Reference(s)
// - Subdivision Surfaces
// https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/thesis-10.pdf
// https://en.wikipedia.org/wiki/Loop_subdivision_surface
// https://cseweb.ucsd.edu/~alchern/teaching/cse167_fa21/6-3Surfaces.pdf
//
// - Original three.js SubdivisionModifier, r124 (Loop)
// https://github.com/mrdoob/three.js/blob/r124/examples/jsm/modifiers/SubdivisionModifier.js
//
// - Original three.js SubdivisionModifier, r59 (Catmull-Clark)
// https://github.com/mrdoob/three.js/blob/r59/examples/js/modifiers/SubdivisionModifier.js
//
/////////////////////////////////////////////////////////////////////////////////////
/**
* @description Loop Subdivision Surface
* @about Smooth subdivision surface modifier for use with three.js BufferGeometry.
* @author Stephens Nunnally <@stevinz>
* @license MIT - Copyright (c) 2022 Stephens Nunnally
* @source https://github.com/stevinz/three-subdivide
*/
window.loopSubdivision={};(()=>{const POSITION_DECIMALS=2;const _average=new THREE.Vector3();const _center=new THREE.Vector3();const _midpoint=new THREE.Vector3();const _normal=new THREE.Vector3();const _temp=new THREE.Vector3();const _vector0=new THREE.Vector3();const _vector1=new THREE.Vector3();const _vector2=new THREE.Vector3();const _vec0to1=new THREE.Vector3();const _vec1to2=new THREE.Vector3();const _vec2to0=new THREE.Vector3();const _position=[new THREE.Vector3(),new THREE.Vector3(),new THREE.Vector3(),];const _vertex=[new THREE.Vector3(),new THREE.Vector3(),new THREE.Vector3(),];const _triangle=new THREE.Triangle();function modify(bufferGeometry,iterations=1,params={}){if(arguments.length>3)console.warn(`modify() now uses a parameter object. See readme for more info!`);if(typeof params!=='object')params={};if(params.split===undefined)params.split=!0;if(params.uvSmooth===undefined)params.uvSmooth=!1;if(params.preserveEdges===undefined)params.preserveEdges=!1;if(params.flatOnly===undefined)params.flatOnly=!1;if(params.maxTriangles===undefined)params.maxTriangles=Infinity;if(!verifyGeometry(bufferGeometry))return bufferGeometry;let modifiedGeometry=bufferGeometry.clone();if(params.split){const splitGeometry=edgeSplit(modifiedGeometry)
modifiedGeometry.dispose();modifiedGeometry=splitGeometry}
for(let i=0;i<iterations;i++){let currentTriangles=modifiedGeometry.attributes.position.count/3;if(currentTriangles<params.maxTriangles){let subdividedGeometry;if(params.flatOnly){subdividedGeometry=flat(modifiedGeometry)}else{subdividedGeometry=smooth(modifiedGeometry,params)}
modifiedGeometry.groups.forEach((group)=>{subdividedGeometry.addGroup(group.start*4,group.count*4,group.materialIndex)});modifiedGeometry.dispose();modifiedGeometry=subdividedGeometry}}
return modifiedGeometry}
window.loopSubdivision.modify=modify;function edgeSplit(geometry){if(!verifyGeometry(geometry))return geometry;const existing=(geometry.index!==null)?geometry.toNonIndexed():geometry.clone();const split=new THREE.BufferGeometry();const attributeList=gatherAttributes(existing);const vertexCount=existing.attributes.position.count;const posAttribute=existing.getAttribute('position');const norAttribute=existing.getAttribute('normal');const edgeHashToTriangle={};const triangleEdgeHashes=[];const edgeLength={};const triangleExist=[];for(let i=0;i<vertexCount;i+=3){_vector0.fromBufferAttribute(posAttribute,i+0);_vector1.fromBufferAttribute(posAttribute,i+1);_vector2.fromBufferAttribute(posAttribute,i+2);_normal.fromBufferAttribute(norAttribute,i);const vecHash0=hashFromVector(_vector0);const vecHash1=hashFromVector(_vector1);const vecHash2=hashFromVector(_vector2);const triangleSize=_triangle.set(_vector0,_vector1,_vector2).getArea();triangleExist.push(!fuzzy(triangleSize,0));if(!triangleExist[i/3]){triangleEdgeHashes.push([]);continue}
calcNormal(_normal,_vector0,_vector1,_vector2);const normalHash=hashFromVector(_normal);const hashes=[`${vecHash0}_${vecHash1}_${normalHash}`,`${vecHash1}_${vecHash0}_${normalHash}`,`${vecHash1}_${vecHash2}_${normalHash}`,`${vecHash2}_${vecHash1}_${normalHash}`,`${vecHash2}_${vecHash0}_${normalHash}`,`${vecHash0}_${vecHash2}_${normalHash}`,];const index=i/3;for(let j=0;j<hashes.length;j++){if(!edgeHashToTriangle[hashes[j]])edgeHashToTriangle[hashes[j]]=[];edgeHashToTriangle[hashes[j]].push(index);if(!edgeLength[hashes[j]]){if(j===0||j===1)edgeLength[hashes[j]]=_vector0.distanceTo(_vector1);if(j===2||j===3)edgeLength[hashes[j]]=_vector1.distanceTo(_vector2);if(j===4||j===5)edgeLength[hashes[j]]=_vector2.distanceTo(_vector0)}}
triangleEdgeHashes.push([hashes[0],hashes[2],hashes[4]])}
attributeList.forEach((attributeName)=>{const attribute=existing.getAttribute(attributeName);if(!attribute)return;const floatArray=splitAttribute(attribute,attributeName);split.setAttribute(attributeName,new THREE.BufferAttribute(floatArray,attribute.itemSize))});const morphAttributes=existing.morphAttributes;for(const attributeName in morphAttributes){const array=[];const morphAttribute=morphAttributes[attributeName];for(let i=0,l=morphAttribute.length;i<l;i++){if(morphAttribute[i].count!==vertexCount)continue;const floatArray=splitAttribute(morphAttribute[i],attributeName,!0);array.push(new THREE.BufferAttribute(floatArray,morphAttribute[i].itemSize))}
split.morphAttributes[attributeName]=array}
split.morphTargetsRelative=existing.morphTargetsRelative;existing.dispose();return split;function splitAttribute(attribute,attributeName,morph=!1){const newTriangles=4;const arrayLength=(vertexCount*attribute.itemSize)*newTriangles;const floatArray=new attribute.array.constructor(arrayLength);const processGroups=(attributeName==='position'&&!morph&&existing.groups.length>0);let groupStart=undefined,groupMaterial=undefined;let index=0;let skipped=0;let step=attribute.itemSize;for(let i=0;i<vertexCount;i+=3){if(!triangleExist[i/3]){skipped+=3;continue}
_vector0.fromBufferAttribute(attribute,i+0);_vector1.fromBufferAttribute(attribute,i+1);_vector2.fromBufferAttribute(attribute,i+2);const existingIndex=i/3;const edgeHash0to1=triangleEdgeHashes[existingIndex][0];const edgeHash1to2=triangleEdgeHashes[existingIndex][1];const edgeHash2to0=triangleEdgeHashes[existingIndex][2];const edgeCount0to1=edgeHashToTriangle[edgeHash0to1].length;const edgeCount1to2=edgeHashToTriangle[edgeHash1to2].length;const edgeCount2to0=edgeHashToTriangle[edgeHash2to0].length;const sharedCount=(edgeCount0to1+edgeCount1to2+edgeCount2to0)-3;const loopStartIndex=((index*3)/step)/3;if(sharedCount===0){setTriangle(floatArray,index,step,_vector0,_vector1,_vector2);index+=(step*3)}else{const length0to1=edgeLength[edgeHash0to1];const length1to2=edgeLength[edgeHash1to2];const length2to0=edgeLength[edgeHash2to0];if((length0to1>length1to2||edgeCount1to2<=1)&&(length0to1>length2to0||edgeCount2to0<=1)&&edgeCount0to1>1){_center.copy(_vector0).add(_vector1).divideScalar(2.0);if(edgeCount2to0>1){_midpoint.copy(_vector2).add(_vector0).divideScalar(2.0);setTriangle(floatArray,index,step,_vector0,_center,_midpoint);index+=(step*3);setTriangle(floatArray,index,step,_center,_vector2,_midpoint);index+=(step*3)}else{setTriangle(floatArray,index,step,_vector0,_center,_vector2);index+=(step*3)}
if(edgeCount1to2>1){_midpoint.copy(_vector1).add(_vector2).divideScalar(2.0);setTriangle(floatArray,index,step,_center,_vector1,_midpoint);index+=(step*3);setTriangle(floatArray,index,step,_midpoint,_vector2,_center);index+=(step*3)}else{setTriangle(floatArray,index,step,_vector1,_vector2,_center);index+=(step*3)}}else if((length1to2>length2to0||edgeCount2to0<=1)&&edgeCount1to2>1){_center.copy(_vector1).add(_vector2).divideScalar(2.0);if(edgeCount0to1>1){_midpoint.copy(_vector0).add(_vector1).divideScalar(2.0);setTriangle(floatArray,index,step,_center,_midpoint,_vector1);index+=(step*3);setTriangle(floatArray,index,step,_midpoint,_center,_vector0);index+=(step*3)}else{setTriangle(floatArray,index,step,_vector1,_center,_vector0);index+=(step*3)}
if(edgeCount2to0>1){_midpoint.copy(_vector2).add(_vector0).divideScalar(2.0);setTriangle(floatArray,index,step,_center,_vector2,_midpoint);index+=(step*3);setTriangle(floatArray,index,step,_midpoint,_vector0,_center);index+=(step*3)}else{setTriangle(floatArray,index,step,_vector2,_vector0,_center);index+=(step*3)}}else if(edgeCount2to0>1){_center.copy(_vector2).add(_vector0).divideScalar(2.0);if(edgeCount1to2>1){_midpoint.copy(_vector1).add(_vector2).divideScalar(2.0);setTriangle(floatArray,index,step,_vector2,_center,_midpoint);index+=(step*3);setTriangle(floatArray,index,step,_center,_vector1,_midpoint);index+=(step*3)}else{setTriangle(floatArray,index,step,_vector2,_center,_vector1);index+=(step*3)}
if(edgeCount0to1>1){_midpoint.copy(_vector0).add(_vector1).divideScalar(2.0);setTriangle(floatArray,index,step,_vector0,_midpoint,_center);index+=(step*3);setTriangle(floatArray,index,step,_midpoint,_vector1,_center);index+=(step*3)}else{setTriangle(floatArray,index,step,_vector0,_vector1,_center);index+=(step*3)}}else{setTriangle(floatArray,index,step,_vector0,_vector1,_vector2);index+=(step*3)}}
if(processGroups){existing.groups.forEach((group)=>{if(group.start===(i-skipped)){if(groupStart!==undefined&&groupMaterial!==undefined){split.addGroup(groupStart,loopStartIndex-groupStart,groupMaterial)}
groupStart=loopStartIndex;groupMaterial=group.materialIndex}})}
skipped=0}
const reducedCount=(index*3)/step;const reducedArray=new attribute.array.constructor(reducedCount);for(let i=0;i<reducedCount;i++){reducedArray[i]=floatArray[i]}
if(processGroups&&groupStart!==undefined&&groupMaterial!==undefined){split.addGroup(groupStart,(((index*3)/step)/3)-groupStart,groupMaterial)}
return reducedArray}}
function flat(geometry){if(!verifyGeometry(geometry))return geometry;const existing=(geometry.index!==null)?geometry.toNonIndexed():geometry.clone();const loop=new THREE.BufferGeometry();const attributeList=gatherAttributes(existing);const vertexCount=existing.attributes.position.count;attributeList.forEach((attributeName)=>{const attribute=existing.getAttribute(attributeName);if(!attribute)return;loop.setAttribute(attributeName,flatAttribute(attribute,vertexCount))});const morphAttributes=existing.morphAttributes;for(const attributeName in morphAttributes){const array=[];const morphAttribute=morphAttributes[attributeName];for(let i=0,l=morphAttribute.length;i<l;i++){if(morphAttribute[i].count!==vertexCount)continue;array.push(flatAttribute(morphAttribute[i],vertexCount))}
loop.morphAttributes[attributeName]=array}
loop.morphTargetsRelative=existing.morphTargetsRelative;existing.dispose();return loop}
function flatAttribute(attribute,vertexCount){const newTriangles=4;const arrayLength=(vertexCount*attribute.itemSize)*newTriangles;const floatArray=new attribute.array.constructor(arrayLength);let index=0;let step=attribute.itemSize;for(let i=0;i<vertexCount;i+=3){_vector0.fromBufferAttribute(attribute,i+0);_vector1.fromBufferAttribute(attribute,i+1);_vector2.fromBufferAttribute(attribute,i+2);_vec0to1.copy(_vector0).add(_vector1).divideScalar(2.0);_vec1to2.copy(_vector1).add(_vector2).divideScalar(2.0);_vec2to0.copy(_vector2).add(_vector0).divideScalar(2.0);setTriangle(floatArray,index,step,_vector0,_vec0to1,_vec2to0);index+=(step*3);setTriangle(floatArray,index,step,_vector1,_vec1to2,_vec0to1);index+=(step*3);setTriangle(floatArray,index,step,_vector2,_vec2to0,_vec1to2);index+=(step*3);setTriangle(floatArray,index,step,_vec0to1,_vec1to2,_vec2to0);index+=(step*3)}
return new THREE.BufferAttribute(floatArray,attribute.itemSize)}
function smooth(geometry,params={}){if(typeof params!=='object')params={};if(params.uvSmooth===undefined)params.uvSmooth=!1;if(params.preserveEdges===undefined)params.preserveEdges=!1;if(!verifyGeometry(geometry))return geometry;const existing=(geometry.index!==null)?geometry.toNonIndexed():geometry.clone();const flatGeometry=flat(existing);const loop=new THREE.BufferGeometry();const attributeList=gatherAttributes(existing);const vertexCount=existing.attributes.position.count;const posAttribute=existing.getAttribute('position');const flatPosition=flatGeometry.getAttribute('position');const hashToIndex={};const existingNeighbors={};const flatOpposites={};const existingEdges={};function addNeighbor(posHash,neighborHash,index){if(!existingNeighbors[posHash])existingNeighbors[posHash]={};if(!existingNeighbors[posHash][neighborHash])existingNeighbors[posHash][neighborHash]=[];existingNeighbors[posHash][neighborHash].push(index)}
function addOpposite(posHash,index){if(!flatOpposites[posHash])flatOpposites[posHash]=[];flatOpposites[posHash].push(index)}
function addEdgePoint(posHash,edgeHash){if(!existingEdges[posHash])existingEdges[posHash]=new Set();existingEdges[posHash].add(edgeHash)}
for(let i=0;i<vertexCount;i+=3){const posHash0=hashFromVector(_vertex[0].fromBufferAttribute(posAttribute,i+0));const posHash1=hashFromVector(_vertex[1].fromBufferAttribute(posAttribute,i+1));const posHash2=hashFromVector(_vertex[2].fromBufferAttribute(posAttribute,i+2));addNeighbor(posHash0,posHash1,i+1);addNeighbor(posHash0,posHash2,i+2);addNeighbor(posHash1,posHash0,i+0);addNeighbor(posHash1,posHash2,i+2);addNeighbor(posHash2,posHash0,i+0);addNeighbor(posHash2,posHash1,i+1);_vec0to1.copy(_vertex[0]).add(_vertex[1]).divideScalar(2.0);_vec1to2.copy(_vertex[1]).add(_vertex[2]).divideScalar(2.0);_vec2to0.copy(_vertex[2]).add(_vertex[0]).divideScalar(2.0);const hash0to1=hashFromVector(_vec0to1);const hash1to2=hashFromVector(_vec1to2);const hash2to0=hashFromVector(_vec2to0);addOpposite(hash0to1,i+2);addOpposite(hash1to2,i+0);addOpposite(hash2to0,i+1);addEdgePoint(posHash0,hash0to1);addEdgePoint(posHash0,hash2to0);addEdgePoint(posHash1,hash0to1);addEdgePoint(posHash1,hash1to2);addEdgePoint(posHash2,hash1to2);addEdgePoint(posHash2,hash2to0)}
for(let i=0;i<flatGeometry.attributes.position.count;i++){const posHash=hashFromVector(_temp.fromBufferAttribute(flatPosition,i));if(!hashToIndex[posHash])hashToIndex[posHash]=[];hashToIndex[posHash].push(i)}
attributeList.forEach((attributeName)=>{const existingAttribute=existing.getAttribute(attributeName);const flatAttribute=flatGeometry.getAttribute(attributeName);if(existingAttribute===undefined||flatAttribute===undefined)return;const floatArray=subdivideAttribute(attributeName,existingAttribute,flatAttribute);loop.setAttribute(attributeName,new THREE.BufferAttribute(floatArray,flatAttribute.itemSize))});const morphAttributes=existing.morphAttributes;for(const attributeName in morphAttributes){const array=[];const morphAttribute=morphAttributes[attributeName];for(let i=0,l=morphAttribute.length;i<l;i++){if(morphAttribute[i].count!==vertexCount)continue;const existingAttribute=morphAttribute[i];const flatAttribute=flatAttribute(morphAttribute[i],morphAttribute[i].count)
const floatArray=subdivideAttribute(attributeName,existingAttribute,flatAttribute);array.push(new THREE.BufferAttribute(floatArray,flatAttribute.itemSize))}
loop.morphAttributes[attributeName]=array}
loop.morphTargetsRelative=existing.morphTargetsRelative;flatGeometry.dispose();existing.dispose();return loop;function subdivideAttribute(attributeName,existingAttribute,flatAttribute){const arrayLength=(flatGeometry.attributes.position.count*flatAttribute.itemSize);const floatArray=new existingAttribute.array.constructor(arrayLength);let index=0;for(let i=0;i<flatGeometry.attributes.position.count;i+=3){for(let v=0;v<3;v++){if(attributeName==='uv'&&!params.uvSmooth){_vertex[v].fromBufferAttribute(flatAttribute,i+v)}else if(attributeName==='normal'){_position[v].fromBufferAttribute(flatPosition,i+v);const positionHash=hashFromVector(_position[v]);const positions=hashToIndex[positionHash];const k=Object.keys(positions).length;const beta=0.75/k;const startWeight=1.0-(beta*k);_vertex[v].fromBufferAttribute(flatAttribute,i+v);_vertex[v].multiplyScalar(startWeight);positions.forEach(positionIndex=>{_average.fromBufferAttribute(flatAttribute,positionIndex);_average.multiplyScalar(beta);_vertex[v].add(_average)})}else{_vertex[v].fromBufferAttribute(flatAttribute,i+v);_position[v].fromBufferAttribute(flatPosition,i+v);const positionHash=hashFromVector(_position[v]);const neighbors=existingNeighbors[positionHash];const opposites=flatOpposites[positionHash];if(neighbors){if(params.preserveEdges){const edgeSet=existingEdges[positionHash];let hasPair=!0;for(const edgeHash of edgeSet){if(flatOpposites[edgeHash].length%2!==0)hasPair=!1}
if(!hasPair)continue}
const k=Object.keys(neighbors).length;const beta=1/k*((5/8)-Math.pow((3/8)+(1/4)*Math.cos(2*Math.PI/k),2));const startWeight=1.0-(beta*k);_vertex[v].multiplyScalar(startWeight);for(let neighborHash in neighbors){const neighborIndices=neighbors[neighborHash];_average.set(0,0,0);for(let j=0;j<neighborIndices.length;j++){_average.add(_temp.fromBufferAttribute(existingAttribute,neighborIndices[j]))}
_average.divideScalar(neighborIndices.length);_average.multiplyScalar(beta);_vertex[v].add(_average)}}else if(opposites&&opposites.length===2){const k=opposites.length;const beta=0.125;const startWeight=1.0-(beta*k);_vertex[v].multiplyScalar(startWeight);opposites.forEach(oppositeIndex=>{_average.fromBufferAttribute(existingAttribute,oppositeIndex);_average.multiplyScalar(beta);_vertex[v].add(_average)})}}}
setTriangle(floatArray,index,flatAttribute.itemSize,_vertex[0],_vertex[1],_vertex[2]);index+=(flatAttribute.itemSize*3)}
return floatArray}}
const _positionShift=Math.pow(10,POSITION_DECIMALS);function fuzzy(a,b,tolerance=0.00001){return((a<(b+tolerance))&&(a>(b-tolerance)))}
function hashFromNumber(num,shift=_positionShift){let roundedNumber=round(num*shift);if(roundedNumber==0)roundedNumber=0;return `${roundedNumber}`}
function hashFromVector(vector,shift=_positionShift){return `${hashFromNumber(vector.x, shift)},${hashFromNumber(vector.y, shift)},${hashFromNumber(vector.z, shift)}`}
function round(x){return(x+((x>0)?0.5:-0.5))<<0}
function calcNormal(target,vec1,vec2,vec3){_temp.subVectors(vec1,vec2);target.subVectors(vec2,vec3);target.cross(_temp).normalize()}
function gatherAttributes(geometry){const desired=['position','normal','uv'];const contains=Object.keys(geometry.attributes);const attributeList=Array.from(new Set(desired.concat(contains)));return attributeList}
function setTriangle(positions,index,step,vec0,vec1,vec2){if(step>=1){positions[index+0+(step*0)]=vec0.x;positions[index+0+(step*1)]=vec1.x;positions[index+0+(step*2)]=vec2.x}
if(step>=2){positions[index+1+(step*0)]=vec0.y;positions[index+1+(step*1)]=vec1.y;positions[index+1+(step*2)]=vec2.y}
if(step>=3){positions[index+2+(step*0)]=vec0.z;positions[index+2+(step*1)]=vec1.z;positions[index+2+(step*2)]=vec2.z}
if(step>=4){positions[index+3+(step*0)]=vec0.w;positions[index+3+(step*1)]=vec1.w;positions[index+3+(step*2)]=vec2.w}}
function verifyGeometry(geometry){if(geometry===undefined){console.warn(`window.loopSubdivision: Geometry provided is undefined`);return!1}
if(!geometry.isBufferGeometry){console.warn(`window.loopSubdivision: Geometry provided is not 'BufferGeometry' type`);return!1}
if(geometry.attributes.position===undefined){console.warn(`window.loopSubdivision: Geometry provided missing required 'position' attribute`);return!1}
if(geometry.attributes.normal===undefined){geometry.computeVertexNormals()}
return!0}})()

View file

@ -1,5 +1,25 @@
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;a<c;a++,r++)g.x=q.getX(a),g.y=q.getY(a),g.z=q.getZ(a),g.applyMatrix4(e.matrixWorld),f+="v "+g.x+" "+g.y+" "+g.z+"\n";if(void 0!==m)for(a=0,c=m.count;a<c;a++,d++)u.x=m.getX(a),u.y=m.getY(a),f+="vt "+u.x+" "+u.y+"\n";if(void 0!==l)for(z.getNormalMatrix(e.matrixWorld),a=0,c=l.count;a<c;a++,t++)p.x=l.getX(a),p.y=l.getY(a),p.z=l.getZ(a),p.applyMatrix3(z).normalize(),f+="vn "+p.x+" "+p.y+" "+p.z+"\n";if(null!==
h)for(a=0,c=h.count;a<c;a+=3){for(k=0;3>k;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;a<c;a+=3){for(k=0;3>k;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<c;a++,r++)g.x=d.getX(a),g.y=d.getY(a),g.z=d.getZ(a),g.applyMatrix4(e.matrixWorld),f+="v "+g.x+" "+g.y+" "+g.z+"\n";if("Line"===t){f+="l ";b=1;for(c=d.count;b<=c;b++)f+=n+b+" ";f+="\n"}if("LineSegments"===t)for(b=1,y=b+1,c=d.count;b<c;b+=2,y=b+1)f+="l "+(n+b)+" "+(n+y)+"\n"}else console.warn("THREE.OBJExporter.parseLine(): geometry type unsupported",d);n+=r}});return f}};
(function(){class OBJExporter{parse(object){let output='';let indexVertex=0;let indexVertexUvs=0;let indexNormals=0;const vertex=new THREE.Vector3();const color=new THREE.Color();const normal=new THREE.Vector3();const uv=new THREE.Vector2();const face=[];function parseMesh(mesh){let nbVertex=0;let nbNormals=0;let nbVertexUvs=0;const geometry=mesh.geometry;const normalMatrixWorld=new THREE.Matrix3();if(geometry.isBufferGeometry!==!0){throw new Error('THREE.OBJExporter: Geometry is not of type THREE.BufferGeometry.')}
const vertices=geometry.getAttribute('position');const normals=geometry.getAttribute('normal');const uvs=geometry.getAttribute('uv');const indices=geometry.getIndex();output+='o '+mesh.name+'\n';if(mesh.material&&mesh.material.name){output+='usemtl '+mesh.material.name+'\n'}
if(vertices!==undefined){for(let i=0,l=vertices.count;i<l;i ++,nbVertex ++){vertex.fromBufferAttribute(vertices,i);vertex.applyMatrix4(mesh.matrixWorld);output+='v '+vertex.x+' '+vertex.y+' '+vertex.z+'\n'}}
if(uvs!==undefined){for(let i=0,l=uvs.count;i<l;i ++,nbVertexUvs ++){uv.fromBufferAttribute(uvs,i);output+='vt '+uv.x+' '+uv.y+'\n'}}
if(normals!==undefined){normalMatrixWorld.getNormalMatrix(mesh.matrixWorld);for(let i=0,l=normals.count;i<l;i ++,nbNormals ++){normal.fromBufferAttribute(normals,i);normal.applyMatrix3(normalMatrixWorld).normalize();output+='vn '+normal.x+' '+normal.y+' '+normal.z+'\n'}}
if(indices!==null){for(let i=0,l=indices.count;i<l;i+=3){for(let m=0;m<3;m ++){const j=indices.getX(i+m)+1;face[m]=indexVertex+j+(normals||uvs?'/'+(uvs?indexVertexUvs+j:'')+(normals?'/'+(indexNormals+j):''):'')}
output+='f '+face.join(' ')+'\n'}}else{for(let i=0,l=vertices.count;i<l;i+=3){for(let m=0;m<3;m ++){const j=i+m+1;face[m]=indexVertex+j+(normals||uvs?'/'+(uvs?indexVertexUvs+j:'')+(normals?'/'+(indexNormals+j):''):'')}
output+='f '+face.join(' ')+'\n'}}
indexVertex+=nbVertex;indexVertexUvs+=nbVertexUvs;indexNormals+=nbNormals}
function parseLine(line){let nbVertex=0;const geometry=line.geometry;const type=line.type;if(geometry.isBufferGeometry!==!0){throw new Error('THREE.OBJExporter: Geometry is not of type THREE.BufferGeometry.')}
const vertices=geometry.getAttribute('position');output+='o '+line.name+'\n';if(vertices!==undefined){for(let i=0,l=vertices.count;i<l;i ++,nbVertex ++){vertex.fromBufferAttribute(vertices,i);vertex.applyMatrix4(line.matrixWorld);output+='v '+vertex.x+' '+vertex.y+' '+vertex.z+'\n'}}
if(type==='Line'){output+='l ';for(let j=1,l=vertices.count;j<=l;j ++){output+=indexVertex+j+' '}
output+='\n'}
if(type==='LineSegments'){for(let j=1,k=j+1,l=vertices.count;j<l;j+=2,k=j+1){output+='l '+(indexVertex+j)+' '+(indexVertex+k)+'\n'}}
indexVertex+=nbVertex}
function parsePoints(points){let nbVertex=0;const geometry=points.geometry;if(geometry.isBufferGeometry!==!0){throw new Error('THREE.OBJExporter: Geometry is not of type THREE.BufferGeometry.')}
const vertices=geometry.getAttribute('position');const colors=geometry.getAttribute('color');output+='o '+points.name+'\n';if(vertices!==undefined){for(let i=0,l=vertices.count;i<l;i ++,nbVertex ++){vertex.fromBufferAttribute(vertices,i);vertex.applyMatrix4(points.matrixWorld);output+='v '+vertex.x+' '+vertex.y+' '+vertex.z;if(colors!==undefined){color.fromBufferAttribute(colors,i).convertLinearToSRGB();output+=' '+color.r+' '+color.g+' '+color.b}
output+='\n'}
output+='p ';for(let j=1,l=vertices.count;j<=l;j ++){output+=indexVertex+j+' '}
output+='\n'}
indexVertex+=nbVertex}
object.traverse(function(child){if(child.isMesh===!0){parseMesh(child)}
if(child.isLine===!0){parseLine(child)}
if(child.isPoints===!0){parsePoints(child)}});return output}}
THREE.OBJExporter=OBJExporter})()

File diff suppressed because one or more lines are too long

1016
libs/three.min.js vendored

File diff suppressed because one or more lines are too long

View file

@ -11,7 +11,7 @@ async function saveSVG() {
link.click();
tip(
`${link.download} is saved. Open "Downloads" screen (crtl + J) to check. You can set image scale in options`,
`${link.download} is saved. Open "Downloads" screen (ctrl + J) to check. You can set image scale in options`,
true,
"success",
5000

View file

@ -12,7 +12,11 @@ window.ThreeD = (function () {
waterColor: "#466eab",
extendedWater: 0,
labels3d: 0,
resolution: 2
wireframe: 0,
resolution: 2,
resolutionScale: 2048,
sunColor: "#cccccc",
subdivide: 0
};
// set variables
@ -92,7 +96,11 @@ window.ThreeD = (function () {
const setScale = function (scale) {
options.scale = scale;
geometry.vertices.forEach((v, i) => (v.z = getMeshHeight(i)));
let vertices = geometry.getAttribute('position');
for(let i = 0; i < vertices.count; i++){
vertices.setZ(i,getMeshHeight(i));
}
geometry.setAttribute('position',vertices);
geometry.verticesNeedUpdate = true;
geometry.computeVertexNormals();
geometry.verticesNeedUpdate = false;
@ -100,6 +108,17 @@ window.ThreeD = (function () {
redraw();
};
const setSunColor = function(color){
options.sunColor = color;
spotLight.color = new THREE.Color(color);
render();
}
const setResolutionScale = function (scale) {
options.resolutionScale = scale;
redraw();
};
const setLightness = function (intensity) {
options.lightness = intensity;
ambientLight.intensity = intensity;
@ -148,6 +167,16 @@ window.ThreeD = (function () {
}
};
const toggle3dSubdivision = function(){
options.subdivide = !options.subdivide;
redraw();
}
const toggleWireframe = function () {
options.wireframe = !options.wireframe;
redraw();
};
const setColors = function (sky, water) {
options.skyColor = sky;
scene.background = scene.fog.color = new THREE.Color(sky);
@ -189,16 +218,20 @@ window.ThreeD = (function () {
// light
ambientLight = new THREE.AmbientLight(0xcccccc, options.lightness);
scene.add(ambientLight);
spotLight = new THREE.SpotLight(0xcccccc, 0.8, 2000, 0.8, 0, 0);
spotLight = new THREE.SpotLight(options.sunColor, 0.8, 2000, 0.8, 0, 0);
spotLight.position.set(options.sun.x, options.sun.y, options.sun.z);
spotLight.castShadow = true;
//maybe add a option for this. But changing the option will require to reinstance the spotLight.
spotLight.shadow.mapSize.width = 2048;
spotLight.shadow.mapSize.height = 2048;
scene.add(spotLight);
//scene.add(new THREE.SpotLightHelper(spotLight));
// Rendered
// Renderer
Renderer = new THREE.WebGLRenderer({canvas, antialias: true, preserveDrawingBuffer: true});
Renderer.setSize(canvas.width, canvas.height);
Renderer.shadowMap.enabled = true;
// Renderer.shadowMap.type = THREE.PCFSoftShadowMap;
if (options.extendedWater) extendWater(graphWidth, graphHeight);
createMesh(graphWidth, graphHeight, grid.cellsX, grid.cellsY);
@ -223,7 +256,7 @@ window.ThreeD = (function () {
function textureToSprite(texture, width, height) {
const map = new THREE.TextureLoader().load(texture);
map.anisotropy = Renderer.getMaxAnisotropy();
map.anisotropy = Renderer.capabilities.getMaxAnisotropy();
const material = new THREE.SpriteMaterial({map});
const sprite = new THREE.Sprite(material);
@ -296,7 +329,9 @@ window.ThreeD = (function () {
};
const city_icon_material = new THREE.MeshPhongMaterial({color: cityOptions.iconColor});
city_icon_material.wireframe = options.wireframe;
const town_icon_material = new THREE.MeshPhongMaterial({color: townOptions.iconColor});
town_icon_material.wireframe = options.wireframe;
const city_icon_geometry = new THREE.CylinderGeometry(cityOptions.iconSize * 2, cityOptions.iconSize * 2, cityOptions.iconSize, 16, 1);
const town_icon_geometry = new THREE.CylinderGeometry(townOptions.iconSize * 2, townOptions.iconSize * 2, townOptions.iconSize, 16, 1);
const line_material = new THREE.LineBasicMaterial({color: cityOptions.iconColor});
@ -387,32 +422,81 @@ window.ThreeD = (function () {
lines = [];
}
async function createMeshTextureUrl(){
return new Promise(async (resolve, reject)=>{
const mapOptions = {
noLabels: options.labels3d,
noWater: options.extendedWater,
fullMap: true
};
const url = await getMapURL("mesh",mapOptions);
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
canvas.width = options.resolutionScale;
canvas.height = options.resolutionScale;
const img = new Image();
img.src = url;
img.onload = function(){
ctx.drawImage(img,0,0,canvas.width,canvas.height);
canvas.toBlob((blob)=>{
const blobObj = window.URL.createObjectURL(blob)
window.setTimeout(()=>{
canvas.remove();
window.URL.revokeObjectURL(blobObj);
}, 100);
resolve(blobObj);
})
}
})
}
// create a mesh from pixel data
async function createMesh(width, height, segmentsX, segmentsY) {
const mapOptions = {
noLabels: options.labels3d,
noWater: options.extendedWater,
fullMap: true
};
const url = await getMapURL("mesh", mapOptions);
window.setTimeout(() => window.URL.revokeObjectURL(url), 5000);
if (texture) texture.dispose();
texture = new THREE.TextureLoader().load(url, render);
if(!options.wireframe){
//Try loading skin texture.
texture = new THREE.TextureLoader().load(await createMeshTextureUrl(), render);
texture.needsUpdate = true;
texture.anisotropy = Renderer.capabilities.getMaxAnisotropy();
}
if (material) material.dispose();
material = new THREE.MeshLambertMaterial();
material.map = texture;
material.transparent = true;
if(options.wireframe){
material = new THREE.MeshLambertMaterial();
material.wireframe = true;
}else{
material = new THREE.MeshLambertMaterial();
material.map = texture;
material.transparent = true;
}
if (geometry) geometry.dispose();
geometry = new THREE.PlaneGeometry(width, height, segmentsX - 1, segmentsY - 1);
geometry.vertices.forEach((v, i) => (v.z = getMeshHeight(i)));
let vertices = geometry.getAttribute('position');
for(let i = 0; i < vertices.count; i++){
vertices.setZ(i,getMeshHeight(i));
}
geometry.setAttribute('position',vertices);
geometry.computeVertexNormals();
if (mesh) scene.remove(mesh);
mesh = new THREE.Mesh(geometry, material);
if(options.subdivide){
await loadLoopSubdivision();
const subdivideParams = {
split: true,
uvSmooth: false,
preserveEdges: true,
flatOnly: false,
maxTriangles: Infinity
};
const smoothGeometry = loopSubdivision.modify(geometry,1,subdivideParams);
mesh = new THREE.Mesh(smoothGeometry, material);
}else{
mesh = new THREE.Mesh(geometry, material);
}
mesh.rotation.x = -Math.PI / 2;
mesh.castShadow = true;
mesh.receiveShadow = true;
@ -449,7 +533,7 @@ window.ThreeD = (function () {
noWater: options.extendedWater,
fullMap: true
};
const url = await getMapURL("mesh", mapOptions);
const url = await createMeshTextureUrl();
window.setTimeout(() => window.URL.revokeObjectURL(url), 4000);
texture = new THREE.TextureLoader().load(url, render);
material.map = texture;
@ -579,6 +663,17 @@ window.ThreeD = (function () {
});
}
function loadLoopSubdivision(){
if (window.loopSubdivision) return Promise.resolve(true);
return new Promise(resolve => {
const script = document.createElement("script");
script.src = "libs/loopsubdivison.min.js";
document.head.append(script);
script.onload = () => resolve(true);
script.onerror = () => resolve(false);
});
}
function OrbitControls(camera, domElement) {
if (THREE.OrbitControls) return new THREE.OrbitControls(camera, domElement);
@ -596,7 +691,7 @@ window.ThreeD = (function () {
return new Promise(resolve => {
const script = document.createElement("script");
script.src = "libs/objexporter.min.js";
script.src = "libs/objexporter.min.js?v=1.89.35";
document.head.append(script);
script.onload = () => resolve(new THREE.OBJExporter());
script.onerror = () => resolve(false);
@ -609,11 +704,15 @@ window.ThreeD = (function () {
update,
stop,
options,
setSunColor,
setScale,
setResolutionScale,
setLightness,
setSun,
setRotation,
toggleLabels,
toggle3dSubdivision,
toggleWireframe,
toggleSky,
setResolution,
setColors,

View file

@ -1060,6 +1060,7 @@ function toggle3dOptions() {
document.getElementById("options3dSunX").addEventListener("change", changeSunPosition);
document.getElementById("options3dSunY").addEventListener("change", changeSunPosition);
document.getElementById("options3dSunZ").addEventListener("change", changeSunPosition);
document.getElementById("options3dMeshSkinResolution").addEventListener("change", changeResolutionScale);
document.getElementById("options3dMeshRotationRange").addEventListener("input", changeRotation);
document.getElementById("options3dMeshRotationNumber").addEventListener("change", changeRotation);
document.getElementById("options3dGlobeRotationRange").addEventListener("input", changeRotation);
@ -1069,6 +1070,10 @@ function toggle3dOptions() {
document.getElementById("options3dMeshSky").addEventListener("input", changeColors);
document.getElementById("options3dMeshWater").addEventListener("input", changeColors);
document.getElementById("options3dGlobeResolution").addEventListener("change", changeResolution);
// document.getElementById("options3dMeshWireframeMode").addEventListener("change",toggleWireframe3d);
document.getElementById("options3dSunColor").addEventListener("input", changeSunColor);
document.getElementById("options3dSubdivide").addEventListener("change",toggle3dSubdivision);
function updateValues() {
const globe = document.getElementById("canvas3d").dataset.type === "viewGlobe";
@ -1081,6 +1086,7 @@ function toggle3dOptions() {
options3dSunY.value = ThreeD.options.sun.y;
options3dSunZ.value = ThreeD.options.sun.z;
options3dMeshRotationRange.value = options3dMeshRotationNumber.value = ThreeD.options.rotateMesh;
options3dMeshSkinResolution.value = ThreeD.options.resolutionScale;
options3dGlobeRotationRange.value = options3dGlobeRotationNumber.value = ThreeD.options.rotateGlobe;
options3dMeshLabels3d.value = ThreeD.options.labels3d;
options3dMeshSkyMode.value = ThreeD.options.extendedWater;
@ -1088,6 +1094,8 @@ function toggle3dOptions() {
options3dMeshSky.value = ThreeD.options.skyColor;
options3dMeshWater.value = ThreeD.options.waterColor;
options3dGlobeResolution.value = ThreeD.options.resolution;
options3dSunColor.value = ThreeD.options.sunColor;
options3dSubdivide.value = ThreeD.options.subdivide;
}
function changeHeightScale() {
@ -1095,11 +1103,20 @@ function toggle3dOptions() {
ThreeD.setScale(+this.value);
}
function changeResolutionScale() {
options3dMeshSkinResolution.value = this.value;
ThreeD.setResolutionScale(+this.value);
}
function changeLightness() {
options3dLightnessRange.value = options3dLightnessNumber.value = this.value;
ThreeD.setLightness(this.value / 100);
}
function changeSunColor(){
ThreeD.setSunColor(options3dSunColor.value);
}
function changeSunPosition() {
const x = +options3dSunX.value;
const y = +options3dSunY.value;
@ -1117,6 +1134,14 @@ function toggle3dOptions() {
ThreeD.toggleLabels();
}
function toggle3dSubdivision(){
ThreeD.toggle3dSubdivision();
}
// function toggleWireframe3d() {
// ThreeD.toggleWireframe();
// }
function toggleSkyMode() {
const hide = ThreeD.options.extendedWater;
options3dColorSection.style.display = hide ? "none" : "block";

View file

@ -1,7 +1,11 @@
"use strict";
// version and caching control
const version = "1.89.38"; // generator version, update each time
const version = "1.89.39"; // generator version, update each time
{
document.title += " v" + version;
@ -28,6 +32,7 @@ const version = "1.89.38"; // generator version, update each time
<ul>
<strong>Latest changes:</strong>
<li>New 3D Scene options and quality improvements.</li>
<li>Autosave feature (in Options)</li>
<li>Google translation support (in Options)</li>
<li>Religions can be edited and redrawn like cultures</li>