mirror of
https://github.com/ChocoTaco1/PlayT2.git
synced 2026-01-19 17:44:45 +00:00
1935 lines
63 KiB
HTML
1935 lines
63 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Babylon.js Terrain Loader</title>
|
|
<style>
|
|
body {
|
|
margin: 0;
|
|
overflow: hidden;
|
|
}
|
|
|
|
#renderCanvas {
|
|
width: 100vw;
|
|
/* Use full viewport width */
|
|
height: 100vh;
|
|
/* Use full viewport height */
|
|
touch-action: none;
|
|
}
|
|
|
|
#buttonContainer {
|
|
position: absolute;
|
|
top: 20px;
|
|
left: 10px;
|
|
z-index: 10;
|
|
display: flex;
|
|
flex-direction: column;
|
|
/* Stack buttons vertically */
|
|
gap: 10px;
|
|
/* Space between buttons */
|
|
}
|
|
|
|
#greyscaleCanvas {
|
|
display: block;
|
|
position: absolute;
|
|
bottom: 10px;
|
|
left: 10px;
|
|
z-index: 10;
|
|
border: 1px solid black;
|
|
width: 256px;
|
|
height: 256px;
|
|
}
|
|
#container1 {
|
|
position: absolute;
|
|
bottom: 10px;
|
|
left: 276px;
|
|
z-index: 10;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
}
|
|
|
|
#container2 {
|
|
position: absolute;
|
|
bottom: 10px;
|
|
left: 416px;
|
|
z-index: 10;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
}
|
|
|
|
#container3 {
|
|
position: absolute;
|
|
bottom: 10px;
|
|
left: 556px;
|
|
z-index: 10;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
}
|
|
|
|
|
|
#container4 {
|
|
position: absolute;
|
|
bottom: 10px;
|
|
left: 696px;
|
|
z-index: 10;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
}
|
|
|
|
|
|
#container5 {
|
|
position: absolute;
|
|
bottom: 10px;
|
|
left: 836px;
|
|
z-index: 10;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
}
|
|
|
|
#container6 {
|
|
position: absolute;
|
|
bottom: 10px;
|
|
left: 976px;
|
|
z-index: 10;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
}
|
|
|
|
#container7 {
|
|
position: absolute;
|
|
bottom: 10px;
|
|
left: 1116px;
|
|
z-index: 10;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
}
|
|
|
|
#layer1 {
|
|
display: block;
|
|
position: absolute;
|
|
bottom: 10px;
|
|
left: 0px;
|
|
z-index: 10;
|
|
border: 1px solid black;
|
|
width: 128px;
|
|
height: 128px;
|
|
}
|
|
#layer2 {
|
|
display: block;
|
|
position: absolute;
|
|
bottom: 10px;
|
|
left: 0px;
|
|
z-index: 10;
|
|
border: 1px solid black;
|
|
width: 128px;
|
|
height: 128px;
|
|
}
|
|
#layer3 {
|
|
display: block;
|
|
position: absolute;
|
|
bottom: 10px;
|
|
left: 0px;
|
|
z-index: 10;
|
|
border: 1px solid black;
|
|
width: 128px;
|
|
height: 128px;
|
|
}
|
|
#layer4 {
|
|
display: block;
|
|
position: absolute;
|
|
bottom: 10px;
|
|
left: 0px;
|
|
z-index: 10;
|
|
border: 1px solid black;
|
|
width: 128px;
|
|
height: 128px;
|
|
}
|
|
#layer5 {
|
|
display: block;
|
|
position: absolute;
|
|
bottom: 10px;
|
|
left: 0px;
|
|
z-index: 10;
|
|
border: 1px solid black;
|
|
width: 128px;
|
|
height: 128px;
|
|
}
|
|
#layer6 {
|
|
display: block;
|
|
position: absolute;
|
|
bottom: 10px;
|
|
left: 0px;
|
|
z-index: 10;
|
|
border: 1px solid black;
|
|
width: 128px;
|
|
height: 128px;
|
|
}
|
|
#layer7 {
|
|
display: block;
|
|
position: absolute;
|
|
bottom: 10px;
|
|
left: 0px;
|
|
z-index: 10;
|
|
border: 1px solid black;
|
|
width: 128px;
|
|
height: 128px;
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
<div id="buttonContainer">
|
|
<label for="fileInput" style="color: white; cursor: pointer;">Oriented to match CC map, north being top</label>
|
|
<label for="fileInput" style="color: white; cursor: pointer;">Upload .ter file, note displays scale is 8</label>
|
|
<input type="file" id="fileInput" accept=".ter" style="color:white" />
|
|
<button id="toggleViewButton">Switch to Shaded View</button>
|
|
<label for="scaleSlider" style="color:white" ;>Image Export Scale (1-16): <span id="scaleValue" style="color:white">1</span></label>
|
|
<input type="range" id="scaleSlider" min="1" max="16" value="1" />
|
|
<button id="exportButton">Export 8 Bit PNG Greyscale</button>
|
|
<button id="exportPGMButton">Export 16 Bit PGM</button>
|
|
<span style="font-weight: normal; margin-bottom: 5px; display: block; color: white;">Note text export order matches terrain file</span>
|
|
<button id="exportTextButton">Export Raw Terrain Data Text File</button>
|
|
<span style="font-weight: normal; margin-bottom: 5px; display: block; color: white;">3D Export</span>
|
|
<button id="exportObjButton">Export Terrain as OBJ</button>
|
|
<button id="exportStlButton">Export Terrain as STL</button>
|
|
<div id="minMaxHeightDisplay" style="margin-bottom: 10px; color: white;"></div>
|
|
<span style="font-weight: normal; margin-bottom: 5px; display: block; color: white;">Click And Drag GreyScale Preview To Offset</span>
|
|
|
|
<button id="exportTerButton">Export New .ter With Offset</button>
|
|
<button id="exportTerFixButton">Flat Spot Fix .ter</button>
|
|
<button id="exportTerFixButtonCone">Cone/Flat Spot Fix .ter</button>
|
|
</div>
|
|
<canvas id="renderCanvas"></canvas>
|
|
<canvas id="greyscaleCanvas" width="256" height="256"></canvas> <!-- Canvas for grayscale image -->
|
|
|
|
<div id="container1">
|
|
<canvas id="layer1"></canvas>
|
|
<div id="layerDisplay1" style="margin-bottom: 1px; color: white; font-size: 12px;">Texture 1</div>
|
|
<button id="LayerBtn1" style="margin-bottom: 150px;"">Export Layer 1</button>
|
|
|
|
</div>
|
|
|
|
<div id="container2">
|
|
<canvas id="layer2"></canvas>
|
|
<div id="layerDisplay2" style="margin-bottom: 1px; color: white; font-size: 12px;">Texture 2</div>
|
|
<button id="LayerBtn2" style="margin-bottom: 150px;"">Export Layer 2</button>
|
|
</div>
|
|
|
|
<div id="container3">
|
|
<canvas id="layer3"></canvas>
|
|
<div id="layerDisplay3" style="margin-bottom: 1px; color: white; font-size: 12px;">Texture 3</div>
|
|
<button id="LayerBtn3" style="margin-bottom: 150px;"">Export Layer 3</button>
|
|
</div>
|
|
|
|
|
|
<div id="container4">
|
|
<canvas id="layer4"></canvas>
|
|
<div id="layerDisplay4" style="margin-bottom: 1px; color: white; font-size: 12px;">Texture 4</div>
|
|
<button id="LayerBtn4" style="margin-bottom: 150px;"">Export Layer 4</button>
|
|
</div>
|
|
|
|
<div id="container5">
|
|
<canvas id="layer5"></canvas>
|
|
<div id="layerDisplay5" style="margin-bottom: 1px; color: white; font-size: 12px;">Texture 5</div>
|
|
<button id="LayerBtn5" style="margin-bottom: 150px;"">Export Layer 5</button>
|
|
</div>
|
|
|
|
<div id="container6">
|
|
<canvas id="layer6"></canvas>
|
|
<div id="layerDisplay6" style="margin-bottom: 1px; color: white; font-size: 12px;">Texture 6</div>
|
|
<button id="LayerBtn6" style="margin-bottom: 150px;"">Export Layer 6</button>
|
|
</div>
|
|
|
|
<div id="container7">
|
|
<canvas id="layer7"></canvas>
|
|
<div id="layerDisplay7" style="margin-bottom: 1px; color: white; font-size: 12px;">Dead Stop map</div>
|
|
<button id="LayerBtn7" style="margin-bottom: 150px;"">Export Layer</button>
|
|
</div>
|
|
|
|
|
|
<script src="https://cdn.babylonjs.com/babylon.js"></script>
|
|
<script>
|
|
|
|
const canvas = document.getElementById('renderCanvas');
|
|
const engine = new BABYLON.Engine(canvas, true);
|
|
const scene = new BABYLON.Scene(engine);
|
|
|
|
// Camera and lighting setup
|
|
const camera = new BABYLON.ArcRotateCamera("camera1", Math.PI / 2, Math.PI / 3, 2048, BABYLON.Vector3.Zero(), scene);
|
|
camera.attachControl(canvas, true);
|
|
camera.wheelDeltaPercentage = 0.005; // Adjust this value for faster zoom (default is 0.01)
|
|
|
|
const light = new BABYLON.HemisphericLight("light1", new BABYLON.Vector3(0.0, 1.0, 1.0), scene);
|
|
light.intensity = 0.75;
|
|
|
|
const size = 256; // Size of the terrain
|
|
const scaleFactor = 8; // Scale factor for X and Z axes
|
|
var mHeight = [];
|
|
var mHeightDefault = [];
|
|
var mHeightOffset = [];
|
|
var map; // Reference to the terrain mesh
|
|
var textureNames = [];
|
|
var alphaMap1 = [];
|
|
var alphaMap2 = [];
|
|
var alphaMap3 = [];
|
|
var alphaMap4 = [];
|
|
var alphaMap5 = [];
|
|
var alphaMap6 = [];
|
|
|
|
var alphaMap1d = [];
|
|
var alphaMap2d = [];
|
|
var alphaMap3d = [];
|
|
var alphaMap4d = [];
|
|
var alphaMap5d = [];
|
|
var alphaMap6d = [];
|
|
|
|
var alphaMapOffset1 = [];
|
|
var alphaMapOffset2 = [];
|
|
var alphaMapOffset3 = [];
|
|
var alphaMapOffset4 = [];
|
|
var alphaMapOffset5 = [];
|
|
var alphaMapOffset6 = [];
|
|
|
|
var test = 0;
|
|
var isOffset = 0;
|
|
var dataViewEX = null;
|
|
var alphaOffsetStart = 0;
|
|
var upFileName = null;
|
|
// Function to create terrain mesh using height data
|
|
function createTerrain() {
|
|
// Dispose of the previous terrain mesh if it exists
|
|
if (map) {
|
|
map.dispose();
|
|
}
|
|
|
|
const mapSubX = size; // Points along the X axis (e.g., 256)
|
|
const mapSubZ = size; // Points along the Z axis (e.g., 256)
|
|
const vertices = [];
|
|
const indices = [];
|
|
|
|
// Create vertices for the mesh (flipping on X-axis)
|
|
for (let l = 0; l < mapSubZ; l++) {
|
|
for (let w = 0; w < mapSubX; w++) {
|
|
const x = -(w - mapSubX * 0.5) * 8; // Flip the X coordinate
|
|
const z = (l - mapSubZ * 0.5) * 8; // Each vertex is 8m apart
|
|
const y = mHeight[l * mapSubX + w]; // Use loaded height data
|
|
|
|
// Store the vertex position
|
|
vertices.push(x, y, z);
|
|
}
|
|
}
|
|
|
|
// Create triangles by defining indices (Clockwise winding order)
|
|
for (let l = 0; l < mapSubZ - 1; l++) {
|
|
for (let w = 0; w < mapSubX - 1; w++) {
|
|
const topLeft = l * mapSubX + w;
|
|
const topRight = topLeft + 1;
|
|
const bottomLeft = (l + 1) * mapSubX + w;
|
|
const bottomRight = bottomLeft + 1;
|
|
|
|
// Adjusted triangle definitions (clockwise winding order)
|
|
indices.push(bottomLeft, topRight, topLeft); // First triangle
|
|
indices.push(bottomLeft, bottomRight, topRight); // Second triangle
|
|
}
|
|
}
|
|
|
|
// Create the mesh using the vertices and indices
|
|
const terrainMesh = new BABYLON.Mesh("terrain", scene);
|
|
const vertexData = new BABYLON.VertexData();
|
|
vertexData.positions = vertices;
|
|
vertexData.indices = indices;
|
|
|
|
// Compute normals for smooth shading
|
|
vertexData.normals = [];
|
|
BABYLON.VertexData.ComputeNormals(vertexData.positions, vertexData.indices, vertexData.normals);
|
|
|
|
// Apply the vertex data to the mesh
|
|
vertexData.applyToMesh(terrainMesh);
|
|
|
|
// Create a material with smooth shading
|
|
flatMaterial = new BABYLON.StandardMaterial("flatMaterial", scene);
|
|
flatMaterial.wireframe = false; // Set to false for smooth shaded view
|
|
flatMaterial.diffuseColor = new BABYLON.Color3(0.8, 0.8, 0.8); // Set color to something natural
|
|
flatMaterial.specularColor = new BABYLON.Color3(0.0, 0.0, 0.0); // No specular highlights for smooth shading
|
|
flatMaterial.emissiveColor = new BABYLON.Color3(0, 0, 0); // No emissive color
|
|
|
|
// Apply the material to the terrain mesh
|
|
terrainMesh.material = flatMaterial;
|
|
|
|
// Update the map variable to reference the newly created terrain mesh
|
|
map = terrainMesh;
|
|
generateGreyscaleImage(); // Generate the grayscale image
|
|
}
|
|
|
|
function createTerrain2() {
|
|
isOffset = 1;
|
|
|
|
const mapSubX = size; // Points along the X axis (e.g., 256)
|
|
const mapSubZ = size; // Points along the Z axis (e.g., 256)
|
|
|
|
const vertices = [];
|
|
const indices = [];
|
|
|
|
// Create vertices for the mesh (flipping on X-axis)
|
|
for (let l = 0; l < mapSubZ; l++) {
|
|
for (let w = 0; w < mapSubX; w++) {
|
|
const x = -(w - mapSubX * 0.5) * 8; // Flip the X coordinate
|
|
const z = (l - mapSubZ * 0.5) * 8; // Each vertex is 8m apart
|
|
const y = mHeightOffset[l * mapSubX + w]; // Use loaded height data
|
|
|
|
// Store the vertex position
|
|
vertices.push(x, y, z);
|
|
}
|
|
}
|
|
|
|
// Create triangles by defining indices (Clockwise winding order)
|
|
for (let l = 0; l < mapSubZ - 1; l++) {
|
|
for (let w = 0; w < mapSubX - 1; w++) {
|
|
const topLeft = l * mapSubX + w;
|
|
const topRight = topLeft + 1;
|
|
const bottomLeft = (l + 1) * mapSubX + w;
|
|
const bottomRight = bottomLeft + 1;
|
|
|
|
// Adjusted triangle definitions (clockwise winding order)
|
|
indices.push(bottomLeft, topRight, topLeft); // First triangle
|
|
indices.push(bottomLeft, bottomRight, topRight); // Second triangle
|
|
}
|
|
}
|
|
|
|
if (map) {
|
|
// Update the existing mesh
|
|
const vertexData = new BABYLON.VertexData();
|
|
vertexData.positions = vertices;
|
|
vertexData.indices = indices;
|
|
|
|
// Compute normals for smooth shading
|
|
vertexData.normals = [];
|
|
BABYLON.VertexData.ComputeNormals(vertexData.positions, vertexData.indices, vertexData.normals);
|
|
|
|
// Apply updated vertex data to the existing mesh
|
|
vertexData.applyToMesh(map, true); // 'true' ensures mesh data is updated
|
|
} else {
|
|
// Create the mesh if it doesn't exist
|
|
map = new BABYLON.Mesh("terrain", scene);
|
|
const vertexData = new BABYLON.VertexData();
|
|
vertexData.positions = vertices;
|
|
vertexData.indices = indices;
|
|
|
|
// Compute normals for smooth shading
|
|
vertexData.normals = [];
|
|
BABYLON.VertexData.ComputeNormals(vertexData.positions, vertexData.indices, vertexData.normals);
|
|
|
|
// Apply the vertex data to the mesh
|
|
vertexData.applyToMesh(map);
|
|
|
|
// Create a material with smooth shading and double-sided rendering
|
|
flatMaterial = new BABYLON.StandardMaterial("flatMaterial", scene);
|
|
flatMaterial.wireframe = false; // Set to false for smooth shaded view
|
|
flatMaterial.diffuseColor = new BABYLON.Color3(0.8, 0.8, 0.8); // Set color to something natural
|
|
flatMaterial.specularColor = new BABYLON.Color3(0.0, 0.0, 0.0); // No specular highlights for smooth shading
|
|
flatMaterial.emissiveColor = new BABYLON.Color3(0, 0, 0); // No emissive color
|
|
|
|
// Apply the material to the terrain mesh
|
|
map.material = flatMaterial;
|
|
}
|
|
}
|
|
|
|
// Function to load and parse the .ter file
|
|
function loadTerFile(file) {
|
|
isOffset = 0;
|
|
upFileName = file.name;
|
|
for (let x = 0; x < 6; x++) {
|
|
var imgLayer = document.getElementById('layer'+ (x+1));
|
|
var context = imgLayer.getContext('2d');
|
|
context.clearRect(0, 0, imgLayer.width, imgLayer.height);
|
|
|
|
let id = document.getElementById('layerDisplay' + (x + 1));
|
|
id.textContent = 'Texture ' + (x+1);
|
|
}
|
|
const reader = new FileReader();
|
|
reader.onload = function (e) {
|
|
var buffer = e.target.result;
|
|
var dataView = new DataView(buffer);
|
|
dataViewEX = dataView;
|
|
let offset = 0;
|
|
var version = dataView.getUint8(offset++);
|
|
console.log(`Terrain Version: ${version}`);
|
|
|
|
mHeightDefault = [];
|
|
textureNames = [];
|
|
alphaMap1 = []; alphaMapD1 = [];
|
|
|
|
for (let i = 0; i < size * size; i++) {
|
|
let height = dataView.getUint16(offset, true); // true for little-endian
|
|
height = height * 0.03125; // Convert height to meters
|
|
offset += 2;
|
|
mHeightDefault.push(height);
|
|
}
|
|
|
|
// Flip the heightmap vertically
|
|
mHeight = [];
|
|
for (let row = size - 1; row >= 0; row--) {
|
|
for (let col = 0; col < size; col++) {
|
|
mHeight.push(mHeightDefault[row * size + col]);
|
|
}
|
|
}
|
|
|
|
offset += (256 * 256);
|
|
|
|
for (let i = 0; i < 8; i++) {
|
|
let strSize = dataView.getUint8(offset++);
|
|
let txtname = readString(dataView, offset, strSize);
|
|
offset += strSize;
|
|
txtname = txtname.replace('terrain.', "").trim();
|
|
if(i < 6 && strSize > 0){
|
|
let id = document.getElementById('layerDisplay'+ (i+1));
|
|
id.textContent = txtname;
|
|
textureNames.push(txtname);
|
|
}
|
|
}
|
|
|
|
alphaOffsetStart = offset;
|
|
|
|
alphaMap1 = []; alphaMap1d = [];
|
|
if(textureNames.length > 0){
|
|
for (let x = 0; x < (256 * 256); x++) {
|
|
var alphaMats = dataView.getUint8(offset++);
|
|
alphaMap1d.push(alphaMats);
|
|
}
|
|
|
|
for (let row = size - 1; row >= 0; row--) {
|
|
for (let col = 0; col < size; col++) {
|
|
alphaMap1.push(alphaMap1d[row * size + col]);
|
|
}
|
|
}
|
|
generateAlphaMapImage(alphaMap1d, 1);
|
|
}
|
|
|
|
alphaMap2 = []; alphaMap2d = [];
|
|
if (textureNames.length > 1) {
|
|
for (let x = 0; x < (256 * 256); x++) {
|
|
var alphaMats = dataView.getUint8(offset++);
|
|
alphaMap2d.push(alphaMats);
|
|
}
|
|
|
|
for (let row = size - 1; row >= 0; row--) {
|
|
for (let col = 0; col < size; col++) {
|
|
alphaMap2.push(alphaMap2d[row * size + col]);
|
|
}
|
|
}
|
|
generateAlphaMapImage(alphaMap2, 2);
|
|
}
|
|
|
|
alphaMap3 = []; alphaMap3d = [];
|
|
if (textureNames.length > 2) {
|
|
for (let x = 0; x < (256 * 256); x++) {
|
|
var alphaMats = dataView.getUint8(offset++);
|
|
alphaMap3d.push(alphaMats);
|
|
}
|
|
|
|
for (let row = size - 1; row >= 0; row--) {
|
|
for (let col = 0; col < size; col++) {
|
|
alphaMap3.push(alphaMap3d[row * size + col]);
|
|
}
|
|
}
|
|
generateAlphaMapImage(alphaMap3, 3);
|
|
}
|
|
|
|
alphaMap4 = []; alphaMap4d = [];
|
|
if (textureNames.length > 3) {
|
|
for (let x = 0; x < (256 * 256); x++) {
|
|
var alphaMats = dataView.getUint8(offset++);
|
|
alphaMap4d.push(alphaMats);
|
|
}
|
|
for (let row = size - 1; row >= 0; row--) {
|
|
for (let col = 0; col < size; col++) {
|
|
alphaMap4.push(alphaMap4d[row * size + col]);
|
|
}
|
|
}
|
|
generateAlphaMapImage(alphaMap4, 4);
|
|
}
|
|
|
|
alphaMap5 = []; alphaMap5d = [];
|
|
if (textureNames.length > 4) {
|
|
for (let x = 0; x < (256 * 256); x++) {
|
|
var alphaMats = dataView.getUint8(offset++);
|
|
alphaMap5d.push(alphaMats);
|
|
}
|
|
for (let row = size - 1; row >= 0; row--) {
|
|
for (let col = 0; col < size; col++) {
|
|
alphaMap5.push(alphaMap5d[row * size + col]);
|
|
}
|
|
}
|
|
generateAlphaMapImage(alphaMap5, 5);
|
|
}
|
|
|
|
alphaMap6 = []; alphaMap6d = [];
|
|
if (textureNames.length > 5) {
|
|
for (let x = 0; x < (256 * 256); x++) {
|
|
var alphaMats = dataView.getUint8(offset++);
|
|
alphaMap6d.push(alphaMats);
|
|
}
|
|
for (let row = size - 1; row >= 0; row--) {
|
|
for (let col = 0; col < size; col++) {
|
|
alphaMap6.push(alphaMap6d[row * size + col]);
|
|
}
|
|
}
|
|
generateAlphaMapImage(alphaMap6, 6);
|
|
}
|
|
|
|
generateDeadMapImage(7);
|
|
|
|
// Once data is loaded, create the terrain
|
|
createTerrain();
|
|
};
|
|
|
|
reader.readAsArrayBuffer(file);
|
|
}
|
|
function vectorDistXYZ(x1, y1, z1, x2, y2, z2) {
|
|
let x = parseFloat(x2) - parseFloat(x1);
|
|
let y = parseFloat(y2) - parseFloat(y1);
|
|
let z = parseFloat(z2) - parseFloat(z1);
|
|
return Math.sqrt(x * x + y * y + z * z);
|
|
}
|
|
function outputConeZone(tempMap) {
|
|
var gridsize = 8;
|
|
for (let y = 0; y < 256; y++) {
|
|
for (let x = 0; x < 256; x++) {
|
|
var midx = Math.floor(x / (gridsize * 2)) === 0 ? gridsize : gridsize + (Math.floor(x / (gridsize * 2)) * (gridsize * 2));
|
|
var midy = Math.floor(y / (gridsize * 2)) === 0 ? gridsize : gridsize + (Math.floor(y / (gridsize * 2)) * (gridsize * 2));
|
|
var dist = vectorDistXYZ(x, y, 0, midx, midy, 0) * 8;
|
|
var offset = dist * Math.sin((2 * Math.PI) / 180);
|
|
tempMap[x + (y * 256)] = tempMap[x + (y * 256)] + offset;
|
|
}
|
|
}
|
|
return tempMap;
|
|
}
|
|
|
|
function exportTerFlatSpotCone() {
|
|
var tempMap = (isOffset) ? mHeightOffset.slice() : mHeight.slice();
|
|
tempMap = outputConeZone(tempMap);
|
|
tempMap = spotFix(tempMap);
|
|
|
|
|
|
var alphaMapT1 = (isOffset) ? alphaMapOffset1 : alphaMap1;
|
|
var alphaMapT2 = (isOffset) ? alphaMapOffset2 : alphaMap2;
|
|
var alphaMapT3 = (isOffset) ? alphaMapOffset3 : alphaMap3;
|
|
var alphaMapT4 = (isOffset) ? alphaMapOffset4 : alphaMap4;
|
|
var alphaMapT5 = (isOffset) ? alphaMapOffset5 : alphaMap5;
|
|
var alphaMapT6 = (isOffset) ? alphaMapOffset6 : alphaMap6;
|
|
|
|
let hm = [];
|
|
var tempStartOffSet = alphaOffsetStart;
|
|
for (let row = size - 1; row >= 0; row--) { //flip back to og
|
|
for (let col = 0; col < size; col++) {
|
|
hm.push(tempMap[row * size + col]);
|
|
}
|
|
}
|
|
|
|
let off = 1;// skip the version
|
|
for (let i = 0; i < size * size; i++) {
|
|
var height = Math.floor(hm[i] / 0.03125);
|
|
dataViewEX.setUint16(off, height, true); // true for little-endian
|
|
off += 2;
|
|
}
|
|
|
|
if (textureNames.length > 0) {
|
|
let tA = [];
|
|
for (let row = size - 1; row >= 0; row--) {//flip back to og
|
|
for (let col = 0; col < size; col++) {
|
|
tA.push(alphaMapT1[row * size + col]);
|
|
}
|
|
}
|
|
for (let i = 0; i < size * size; i++) {
|
|
dataViewEX.setUint8(tempStartOffSet++, tA[i]);
|
|
off += 2;
|
|
}
|
|
}
|
|
|
|
if (textureNames.length > 1) {
|
|
let tA = [];
|
|
for (let row = size - 1; row >= 0; row--) {//flip back to og
|
|
for (let col = 0; col < size; col++) {
|
|
tA.push(alphaMapT2[row * size + col]);
|
|
}
|
|
}
|
|
for (let i = 0; i < size * size; i++) {
|
|
dataViewEX.setUint8(tempStartOffSet++, tA[i]);
|
|
off += 2;
|
|
}
|
|
}
|
|
|
|
if (textureNames.length > 2) {
|
|
let tA = [];
|
|
for (let row = size - 1; row >= 0; row--) {//flip back to og
|
|
for (let col = 0; col < size; col++) {
|
|
tA.push(alphaMapT3[row * size + col]);
|
|
}
|
|
}
|
|
for (let i = 0; i < size * size; i++) {
|
|
dataViewEX.setUint8(tempStartOffSet++, tA[i]);
|
|
off += 2;
|
|
}
|
|
}
|
|
|
|
if (textureNames.length > 3) {
|
|
let tA = [];
|
|
for (let row = size - 1; row >= 0; row--) {//flip back to og
|
|
for (let col = 0; col < size; col++) {
|
|
tA.push(alphaMapT4[row * size + col]);
|
|
}
|
|
}
|
|
for (let i = 0; i < size * size; i++) {
|
|
dataViewEX.setUint8(tempStartOffSet++, tA[i]);
|
|
off += 2;
|
|
}
|
|
}
|
|
|
|
if (textureNames.length > 4) {
|
|
let tA = [];
|
|
for (let row = size - 1; row >= 0; row--) {//flip back to og
|
|
for (let col = 0; col < size; col++) {
|
|
tA.push(alphaMapT5[row * size + col]);
|
|
}
|
|
}
|
|
for (let i = 0; i < size * size; i++) {
|
|
dataViewEX.setUint8(tempStartOffSet++, tA[i]);
|
|
off += 2;
|
|
}
|
|
}
|
|
|
|
if (textureNames.length > 5) {
|
|
let tA = [];
|
|
for (let row = size - 1; row >= 0; row--) {//flip back to og
|
|
for (let col = 0; col < size; col++) {
|
|
tA.push(alphaMapT6[row * size + col]);
|
|
}
|
|
}
|
|
for (let i = 0; i < size * size; i++) {
|
|
dataViewEX.setUint8(tempStartOffSet++, tA[i]);
|
|
off += 2;
|
|
}
|
|
}
|
|
|
|
const blob = new Blob([dataViewEX.buffer], { type: "application/octet-stream" });
|
|
const link = document.createElement('a');
|
|
link.href = URL.createObjectURL(blob);
|
|
link.download = upFileName;
|
|
link.click();
|
|
}
|
|
|
|
function exportTerFlatSpot() {
|
|
var tempMap = (isOffset) ? mHeightOffset.slice() : mHeight.slice();
|
|
tempMap = spotFix(tempMap);
|
|
|
|
|
|
var alphaMapT1= (isOffset) ? alphaMapOffset1 : alphaMap1;
|
|
var alphaMapT2 = (isOffset) ? alphaMapOffset2 : alphaMap2;
|
|
var alphaMapT3 = (isOffset) ? alphaMapOffset3 : alphaMap3;
|
|
var alphaMapT4 = (isOffset) ? alphaMapOffset4 : alphaMap4;
|
|
var alphaMapT5 = (isOffset) ? alphaMapOffset5 : alphaMap5;
|
|
var alphaMapT6 = (isOffset) ? alphaMapOffset6 : alphaMap6;
|
|
|
|
let hm = [];
|
|
var tempStartOffSet = alphaOffsetStart;
|
|
for (let row = size - 1; row >= 0; row--) { //flip back to og
|
|
for (let col = 0; col < size; col++) {
|
|
hm.push(tempMap[row * size + col]);
|
|
}
|
|
}
|
|
|
|
let off = 1;// skip the version
|
|
for (let i = 0; i < size * size; i++) {
|
|
var height = Math.floor(hm[i] / 0.03125);
|
|
dataViewEX.setUint16(off, height, true); // true for little-endian
|
|
off += 2;
|
|
}
|
|
|
|
if (textureNames.length > 0) {
|
|
let tA = [];
|
|
for (let row = size - 1; row >= 0; row--) {//flip back to og
|
|
for (let col = 0; col < size; col++) {
|
|
tA.push(alphaMapT1[row * size + col]);
|
|
}
|
|
}
|
|
for (let i = 0; i < size * size; i++) {
|
|
dataViewEX.setUint8(tempStartOffSet++, tA[i]);
|
|
off += 2;
|
|
}
|
|
}
|
|
|
|
if (textureNames.length > 1) {
|
|
let tA = [];
|
|
for (let row = size - 1; row >= 0; row--) {//flip back to og
|
|
for (let col = 0; col < size; col++) {
|
|
tA.push(alphaMapT2[row * size + col]);
|
|
}
|
|
}
|
|
for (let i = 0; i < size * size; i++) {
|
|
dataViewEX.setUint8(tempStartOffSet++, tA[i]);
|
|
off += 2;
|
|
}
|
|
}
|
|
|
|
if (textureNames.length > 2) {
|
|
let tA = [];
|
|
for (let row = size - 1; row >= 0; row--) {//flip back to og
|
|
for (let col = 0; col < size; col++) {
|
|
tA.push(alphaMapT3[row * size + col]);
|
|
}
|
|
}
|
|
for (let i = 0; i < size * size; i++) {
|
|
dataViewEX.setUint8(tempStartOffSet++, tA[i]);
|
|
off += 2;
|
|
}
|
|
}
|
|
|
|
if (textureNames.length > 3) {
|
|
let tA = [];
|
|
for (let row = size - 1; row >= 0; row--) {//flip back to og
|
|
for (let col = 0; col < size; col++) {
|
|
tA.push(alphaMapT4[row * size + col]);
|
|
}
|
|
}
|
|
for (let i = 0; i < size * size; i++) {
|
|
dataViewEX.setUint8(tempStartOffSet++, tA[i]);
|
|
off += 2;
|
|
}
|
|
}
|
|
|
|
if (textureNames.length > 4) {
|
|
let tA = [];
|
|
for (let row = size - 1; row >= 0; row--) {//flip back to og
|
|
for (let col = 0; col < size; col++) {
|
|
tA.push(alphaMapT5[row * size + col]);
|
|
}
|
|
}
|
|
for (let i = 0; i < size * size; i++) {
|
|
dataViewEX.setUint8(tempStartOffSet++, tA[i]);
|
|
off += 2;
|
|
}
|
|
}
|
|
|
|
if (textureNames.length > 5) {
|
|
let tA = [];
|
|
for (let row = size - 1; row >= 0; row--) {//flip back to og
|
|
for (let col = 0; col < size; col++) {
|
|
tA.push(alphaMapT6[row * size + col]);
|
|
}
|
|
}
|
|
for (let i = 0; i < size * size; i++) {
|
|
dataViewEX.setUint8(tempStartOffSet++, tA[i]);
|
|
off += 2;
|
|
}
|
|
}
|
|
|
|
const blob = new Blob([dataViewEX.buffer], { type: "application/octet-stream" });
|
|
const link = document.createElement('a');
|
|
link.href = URL.createObjectURL(blob);
|
|
link.download = upFileName;
|
|
link.click();
|
|
}
|
|
|
|
function exportTer() {
|
|
if(!isOffset)
|
|
return;
|
|
let hm = [];
|
|
var tempStartOffSet = alphaOffsetStart;
|
|
for (let row = size - 1; row >= 0; row--) { //flip back to og
|
|
for (let col = 0; col < size; col++) {
|
|
hm.push(mHeightOffset[row * size + col]);
|
|
}
|
|
}
|
|
|
|
let off = 1;// skip the version
|
|
for (let i = 0; i < size * size; i++) {
|
|
var height = Math.floor(hm[i] / 0.03125);
|
|
dataViewEX.setUint16(off, height, true); // true for little-endian
|
|
off += 2;
|
|
}
|
|
|
|
if (textureNames.length > 0) {
|
|
let tA = [];
|
|
for (let row = size - 1; row >= 0; row--) {//flip back to og
|
|
for (let col = 0; col < size; col++) {
|
|
tA.push(alphaMapOffset1[row * size + col]);
|
|
}
|
|
}
|
|
for (let i = 0; i < size * size; i++) {
|
|
dataViewEX.setUint8(tempStartOffSet++, tA[i]);
|
|
off += 2;
|
|
}
|
|
}
|
|
|
|
if (textureNames.length > 1) {
|
|
let tA = [];
|
|
for (let row = size - 1; row >= 0; row--) {//flip back to og
|
|
for (let col = 0; col < size; col++) {
|
|
tA.push(alphaMapOffset2[row * size + col]);
|
|
}
|
|
}
|
|
for (let i = 0; i < size * size; i++) {
|
|
dataViewEX.setUint8(tempStartOffSet++, tA[i]);
|
|
off += 2;
|
|
}
|
|
}
|
|
|
|
if (textureNames.length > 2) {
|
|
let tA = [];
|
|
for (let row = size - 1; row >= 0; row--) {//flip back to og
|
|
for (let col = 0; col < size; col++) {
|
|
tA.push(alphaMapOffset3[row * size + col]);
|
|
}
|
|
}
|
|
for (let i = 0; i < size * size; i++) {
|
|
dataViewEX.setUint8(tempStartOffSet++, tA[i]);
|
|
off += 2;
|
|
}
|
|
}
|
|
|
|
if (textureNames.length > 3) {
|
|
let tA = [];
|
|
for (let row = size - 1; row >= 0; row--) {//flip back to og
|
|
for (let col = 0; col < size; col++) {
|
|
tA.push(alphaMapOffset4[row * size + col]);
|
|
}
|
|
}
|
|
for (let i = 0; i < size * size; i++) {
|
|
dataViewEX.setUint8(tempStartOffSet++, tA[i]);
|
|
off += 2;
|
|
}
|
|
}
|
|
|
|
if (textureNames.length > 4) {
|
|
let tA = [];
|
|
for (let row = size - 1; row >= 0; row--) {//flip back to og
|
|
for (let col = 0; col < size; col++) {
|
|
tA.push(alphaMapOffset5[row * size + col]);
|
|
}
|
|
}
|
|
for (let i = 0; i < size * size; i++) {
|
|
dataViewEX.setUint8(tempStartOffSet++, tA[i]);
|
|
off += 2;
|
|
}
|
|
}
|
|
|
|
if (textureNames.length > 5) {
|
|
let tA = [];
|
|
for (let row = size - 1; row >= 0; row--) {//flip back to og
|
|
for (let col = 0; col < size; col++) {
|
|
tA.push(alphaMapOffset6[row * size + col]);
|
|
}
|
|
}
|
|
for (let i = 0; i < size * size; i++) {
|
|
dataViewEX.setUint8(tempStartOffSet++, tA[i]);
|
|
off += 2;
|
|
}
|
|
}
|
|
|
|
const blob = new Blob([dataViewEX.buffer], { type: "application/octet-stream" });
|
|
const link = document.createElement('a');
|
|
link.href = URL.createObjectURL(blob);
|
|
link.download = upFileName;
|
|
link.click();
|
|
}
|
|
|
|
function readString(dataView, offset, length) {
|
|
let result = '';
|
|
for (let i = 0; i < length; i++) {
|
|
const byte = dataView.getUint8(offset + i);
|
|
if (byte === 0) break; // Stop at null terminator if present
|
|
result += String.fromCharCode(byte);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// Function to generate a grayscale image from height data
|
|
function generateAlphaMapImage(alphaArray, layer) {
|
|
const targetSize = 128; // Downscaled size
|
|
const imgLayer = document.getElementById('layer' + layer);
|
|
imgLayer.width = targetSize; // Set canvas width
|
|
imgLayer.height = targetSize; // Set canvas height
|
|
const context = imgLayer.getContext('2d');
|
|
|
|
// Clear the canvas
|
|
context.clearRect(0, 0, imgLayer.width, imgLayer.height);
|
|
|
|
// Create ImageData for the target size
|
|
const imageData = context.createImageData(targetSize, targetSize);
|
|
|
|
// Bilinear interpolation loop
|
|
for (let y = 0; y < targetSize; y++) {
|
|
for (let x = 0; x < targetSize; x++) {
|
|
// Map target pixel (x, y) to source coordinates
|
|
const sourceX = (x / targetSize) * (size - 1); // Scaled x-coordinate in source
|
|
const sourceY = (y / targetSize) * (size - 1); // Scaled y-coordinate in source
|
|
|
|
// Find the four nearest pixels in the source
|
|
const x1 = Math.floor(sourceX);
|
|
const x2 = Math.min(Math.ceil(sourceX), size - 1); // Clamp to bounds
|
|
const y1 = Math.floor(sourceY);
|
|
const y2 = Math.min(Math.ceil(sourceY), size - 1); // Clamp to bounds
|
|
|
|
// Values at the four corners
|
|
const v1 = alphaArray[x1 + y1 * size]; // Top-left
|
|
const v2 = alphaArray[x2 + y1 * size]; // Top-right
|
|
const v3 = alphaArray[x1 + y2 * size]; // Bottom-left
|
|
const v4 = alphaArray[x2 + y2 * size]; // Bottom-right
|
|
|
|
// Interpolate to get the value for the target pixel
|
|
const interpolatedValue = bilinearInter(
|
|
x1, y1, x2, y2,
|
|
sourceX, sourceY,
|
|
v1, v2, v3, v4
|
|
);
|
|
|
|
// Assign the interpolated value to the target pixel
|
|
const destIndex = (x + y * targetSize) * 4;
|
|
imageData.data[destIndex] = interpolatedValue; // Red
|
|
imageData.data[destIndex + 1] = 0; // Green
|
|
imageData.data[destIndex + 2] = 0; // Blue
|
|
imageData.data[destIndex + 3] = 255; // Alpha
|
|
}
|
|
}
|
|
|
|
context.putImageData(imageData, 0, 0);
|
|
imgLayer.style.display = 'block'; // Show the grayscale canvas
|
|
}
|
|
|
|
function generateDeadMapImage(layer) {
|
|
const canvas = document.getElementById(`layer${layer}`);
|
|
if (!canvas) return;
|
|
var DMap = [];
|
|
if(!mHeightOffset.length){
|
|
DMap = mHeight;
|
|
}
|
|
else{
|
|
DMap = mHeightOffset;
|
|
}
|
|
const context = canvas.getContext('2d');
|
|
const scaledSize = 128;
|
|
const scaledCanvas = document.createElement('canvas');
|
|
scaledCanvas.width = scaledSize;
|
|
scaledCanvas.height = scaledSize;
|
|
const scaledContext = scaledCanvas.getContext('2d');
|
|
|
|
const minHeight = Math.min(...DMap);
|
|
const maxHeight = Math.max(...DMap);
|
|
const range = maxHeight - minHeight || 1; // Avoid division by zero
|
|
|
|
const scaledImageData = scaledContext.createImageData(scaledSize, scaledSize);
|
|
|
|
for (let i = 0; i < scaledSize; i++) {
|
|
for (let j = 0; j < scaledSize; j++) {
|
|
const x = (i / scaledSize) * size;
|
|
const y = (j / scaledSize) * size;
|
|
|
|
const x1 = Math.floor(x), y1 = Math.floor(y);
|
|
const x2 = Math.min(x1 + 1, size - 1);
|
|
const y2 = Math.min(y1 + 1, size - 1);
|
|
|
|
const v1 = DMap[y1 * size + x1];
|
|
const v2 = DMap[y1 * size + x2];
|
|
const v3 = DMap[y2 * size + x1];
|
|
const v4 = DMap[y2 * size + x2];
|
|
|
|
const interpolatedHeight = bilinearInter(x1, y1, x2, y2, x, y, v1, v2, v3, v4);
|
|
const normalizedHeight = (interpolatedHeight - minHeight) / range;
|
|
const grayValue = Math.floor(normalizedHeight * 255);
|
|
const ang = sNormal(x, y, DMap);
|
|
|
|
const color = getColorFromAngle(ang, grayValue);
|
|
|
|
const index = (j * scaledSize + i) * 4;
|
|
scaledImageData.data.set(color, index);
|
|
}
|
|
}
|
|
|
|
scaledContext.putImageData(scaledImageData, 0, 0);
|
|
context.drawImage(scaledCanvas, 0, 0, canvas.width, canvas.height);
|
|
canvas.style.display = 'block';
|
|
}
|
|
function exportDeadMapImage(layer, scale) {
|
|
const canvas = document.getElementById(`layer${layer}`);
|
|
if (!canvas) return;
|
|
|
|
var DMap = [];
|
|
if (!mHeightOffset.length) {
|
|
DMap = mHeight;
|
|
} else {
|
|
DMap = mHeightOffset;
|
|
}
|
|
|
|
const scaledSize = 256 * scale;
|
|
const scaledCanvas = document.createElement('canvas');
|
|
scaledCanvas.width = scaledSize;
|
|
scaledCanvas.height = scaledSize;
|
|
const scaledContext = scaledCanvas.getContext('2d');
|
|
|
|
const minHeight = Math.min(...DMap);
|
|
const maxHeight = Math.max(...DMap);
|
|
const range = maxHeight - minHeight || 1; // Avoid division by zero
|
|
|
|
const scaledImageData = scaledContext.createImageData(scaledSize, scaledSize);
|
|
|
|
for (let i = 0; i < scaledSize; i++) {
|
|
for (let j = 0; j < scaledSize; j++) {
|
|
const x = (i / scaledSize) * size;
|
|
const y = (j / scaledSize) * size;
|
|
|
|
const x1 = Math.floor(x), y1 = Math.floor(y);
|
|
const x2 = Math.min(x1 + 1, size - 1);
|
|
const y2 = Math.min(y1 + 1, size - 1);
|
|
|
|
const v1 = DMap[y1 * size + x1];
|
|
const v2 = DMap[y1 * size + x2];
|
|
const v3 = DMap[y2 * size + x1];
|
|
const v4 = DMap[y2 * size + x2];
|
|
|
|
const interpolatedHeight = bilinearInter(x1, y1, x2, y2, x, y, v1, v2, v3, v4);
|
|
const normalizedHeight = (interpolatedHeight - minHeight) / range;
|
|
const grayValue = Math.floor(normalizedHeight * 255);
|
|
const ang = sNormal(x1, y1, DMap);
|
|
|
|
const color = getColorFromAngle(ang, grayValue);
|
|
|
|
const index = (j * scaledSize + i) * 4;
|
|
scaledImageData.data.set(color, index);
|
|
}
|
|
}
|
|
|
|
scaledContext.putImageData(scaledImageData, 0, 0);
|
|
|
|
// Create a link to download the image
|
|
const link = document.createElement('a');
|
|
link.download = 'deadstopMap.png';
|
|
link.href = scaledCanvas.toDataURL('image/png');
|
|
link.click();
|
|
}
|
|
|
|
function getColorFromAngle(angle, grayValue) {
|
|
if (angle < 0.2) return [255, 0, 0, 255]; // Red
|
|
if (angle < 0.5) return [0, 255, 0, 255]; // Green
|
|
if (angle < 1) return [0, 0, 255, 255]; // Blue
|
|
return [grayValue, grayValue, grayValue, 255]; // Grayscale
|
|
}
|
|
|
|
function generateGreyscaleImage() {
|
|
const greyscaleCanvas = document.getElementById('greyscaleCanvas');
|
|
const context = greyscaleCanvas.getContext('2d');
|
|
const imageData = context.createImageData(size, size);
|
|
|
|
// Calculate min and max heights for normalization
|
|
const minHeight = Math.min(...mHeight);
|
|
const maxHeight = Math.max(...mHeight);
|
|
const minMaxHeightDisplay = document.getElementById('minMaxHeightDisplay');
|
|
minMaxHeightDisplay.textContent = `Min Height: ${minHeight.toFixed(2)}, Max Height: ${maxHeight.toFixed(2)}`;
|
|
|
|
for (let i = 0; i < mHeight.length; i++) {
|
|
// Normalize height to 0-1 range
|
|
const normalizedHeight = (mHeight[i] - minHeight) / (maxHeight - minHeight);
|
|
// Map to grayscale value (0-255)
|
|
const grayValue = Math.floor(normalizedHeight * 255);
|
|
|
|
imageData.data[i * 4] = grayValue; // Red
|
|
imageData.data[i * 4 + 1] = grayValue; // Green
|
|
imageData.data[i * 4 + 2] = grayValue; // Blue
|
|
imageData.data[i * 4 + 3] = 255; // Alpha
|
|
}
|
|
|
|
context.putImageData(imageData, 0, 0);
|
|
greyscaleCanvas.style.display = 'block'; // Show the grayscale canvas
|
|
// Ensure the drag functionality is enabled
|
|
greyscaleImageData = imageData;
|
|
enableGreyscaleDragging();
|
|
}
|
|
|
|
function exportAlphaMap(layer, scale) {
|
|
let alphaLayer = [];
|
|
switch (layer) {
|
|
case 1:alphaLayer = (!isOffset) ? alphaMap1 : alphaMapOffset1;break;
|
|
case 2:alphaLayer = (!isOffset) ? alphaMap2 : alphaMapOffset2;break;
|
|
case 3:alphaLayer = (!isOffset) ? alphaMap3 : alphaMapOffset3; break;
|
|
case 4:alphaLayer = (!isOffset) ? alphaMap4 : alphaMapOffset4;break;
|
|
case 5:alphaLayer = (!isOffset) ? alphaMap5 : alphaMapOffset5;break;
|
|
case 6:alphaLayer = (!isOffset) ? alphaMap6 : alphaMapOffset6;break;
|
|
}
|
|
if (alphaLayer.length > 0) {
|
|
const resizeTo = size * scale;
|
|
const img1 = new ImageData(resizeTo, resizeTo);
|
|
|
|
for (let y = 0; y < resizeTo; y++) {
|
|
for (let x = 0; x < resizeTo; x++) {
|
|
const nx = Math.floor(x / scale);
|
|
const ny = Math.floor(y / scale);
|
|
const nxedge = (nx !== 255) ? 1 : -255;
|
|
const nyedge = (ny !== 255) ? 1 : -255;
|
|
|
|
const v1 = alphaLayer[nx + (ny * size)];
|
|
const v2 = alphaLayer[(nx + nxedge) + (ny * size)];
|
|
const v3 = alphaLayer[nx + ((ny + nyedge) * size)];
|
|
const v4 = alphaLayer[(nx + (nxedge)) + ((ny + nyedge) * size)];
|
|
|
|
const color = bilinearInter(
|
|
Math.floor(x / scale) * scale,
|
|
Math.floor(y / scale) * scale,
|
|
(Math.floor(x / scale) + 1) * scale,
|
|
(Math.floor(y / scale) + 1) * scale,
|
|
x, y, v1, v2, v3, v4);
|
|
|
|
// Flip the image vertically
|
|
const flippedY = y;
|
|
|
|
img1.data[x * 4 + flippedY * 4 * resizeTo] = color; // Red
|
|
img1.data[x * 4 + flippedY * 4 * resizeTo + 1] = 0; // Green
|
|
img1.data[x * 4 + flippedY * 4 * resizeTo + 2] = 0; // Blue
|
|
img1.data[x * 4 + flippedY * 4 * resizeTo + 3] = 255; // Alpha
|
|
}
|
|
}
|
|
|
|
// Create a new canvas for the scaled image
|
|
const scaledCanvas = document.createElement('canvas');
|
|
scaledCanvas.width = resizeTo;
|
|
scaledCanvas.height = resizeTo;
|
|
const scaledContext = scaledCanvas.getContext('2d');
|
|
scaledContext.putImageData(img1, 0, 0);
|
|
|
|
// Create a link to download the image
|
|
const link = document.createElement('a');
|
|
link.download = textureNames[layer-1] + '.png';
|
|
link.href = scaledCanvas.toDataURL('image/png');
|
|
link.click();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Function to resize and export the grayscale image
|
|
function exportScaledImage() {
|
|
if (isOffset) {
|
|
tempMap = mHeightOffset;
|
|
}
|
|
else {
|
|
tempMap = mHeight;
|
|
}
|
|
const scale = parseInt(document.getElementById('scaleSlider').value, 10);
|
|
const resizeTo = size * scale;
|
|
const img1 = new ImageData(resizeTo, resizeTo);
|
|
const max = Math.max(...tempMap);
|
|
const min = Math.min(...tempMap);
|
|
|
|
for (let y = 0; y < resizeTo; y++) {
|
|
for (let x = 0; x < resizeTo; x++) {
|
|
const nx = Math.floor(x / scale);
|
|
const ny = Math.floor(y / scale);
|
|
const nxedge = (nx !== 255) ? 1 : -255;
|
|
const nyedge = (ny !== 255) ? 1 : -255;
|
|
|
|
const v1 = tempMap[nx + (ny * size)];
|
|
const v2 = tempMap[(nx + nxedge) + (ny * size)];
|
|
const v3 = tempMap[nx + ((ny + nyedge) * size)];
|
|
const v4 = tempMap[(nx + (nxedge)) + ((ny + nyedge) * size)];
|
|
|
|
const linval = bilinearInter(
|
|
Math.floor(x / scale) * scale,
|
|
Math.floor(y / scale) * scale,
|
|
(Math.floor(x / scale) + 1) * scale,
|
|
(Math.floor(y / scale) + 1) * scale,
|
|
x,y,v1,v2,v3,v4);
|
|
|
|
const dif = max - min;
|
|
const colRange = 255 / dif;
|
|
const color = Math.floor((linval - min) * colRange);
|
|
|
|
img1.data[x * 4 + y * 4 * resizeTo] = color; // Red
|
|
img1.data[x * 4 + y * 4 * resizeTo + 1] = color; // Green
|
|
img1.data[x * 4 + y * 4 * resizeTo + 2] = color; // Blue
|
|
img1.data[x * 4 + y * 4 * resizeTo + 3] = 255; // Alpha
|
|
}
|
|
}
|
|
|
|
// Create a new canvas for the scaled image
|
|
const scaledCanvas = document.createElement('canvas');
|
|
scaledCanvas.width = resizeTo;
|
|
scaledCanvas.height = resizeTo;
|
|
const scaledContext = scaledCanvas.getContext('2d');
|
|
scaledContext.putImageData(img1, 0, 0);
|
|
|
|
// Create a link to download the image
|
|
const link = document.createElement('a');
|
|
link.download = 'heightmap.png';
|
|
link.href = scaledCanvas.toDataURL('image/png');
|
|
link.click();
|
|
}
|
|
|
|
// Function to export height data as a P2 (ASCII) PGM file with scaling
|
|
function exportPGM() {
|
|
if(isOffset){
|
|
tempMap = mHeightOffset;
|
|
}
|
|
else{
|
|
tempMap = mHeight;
|
|
}
|
|
const scale = parseInt(document.getElementById('scaleSlider').value, 10);
|
|
const resizeTo = size * scale; // New dimensions based on scale
|
|
const maxHeight = Math.max(...tempMap);
|
|
const minHeight = Math.min(...tempMap);
|
|
const pgmHeader = `P2\n${resizeTo} ${resizeTo}\n65535\n`;
|
|
|
|
let pgmData = '';
|
|
|
|
for (let y = 0; y < resizeTo; y++) {
|
|
for (let x = 0; x < resizeTo; x++) {
|
|
const nx = Math.floor(x / scale);
|
|
const ny = Math.floor(y / scale);
|
|
|
|
// Ensure nx and ny are within bounds
|
|
const nxEdge = (nx < size - 1) ? 1 : 0;
|
|
const nyEdge = (ny < size - 1) ? 1 : 0;
|
|
|
|
const v1 = tempMap[nx + (ny * size)];
|
|
const v2 = tempMap[(nx + nxEdge) + (ny * size)];
|
|
const v3 = tempMap[nx + ((ny + nyEdge) * size)];
|
|
const v4 = tempMap[(nx + nxEdge) + ((ny + nyEdge) * size)];
|
|
|
|
// Bilinear interpolation
|
|
const interpolatedValue = bilinearInter(
|
|
Math.floor(x / scale) * scale,
|
|
Math.floor(y / scale) * scale,
|
|
(Math.floor(x / scale) + 1) * scale,
|
|
(Math.floor(y / scale) + 1) * scale,
|
|
x, y, v1, v2, v3, v4);
|
|
|
|
// Normalize height to the range 0-65535
|
|
const normalizedHeight = Math.floor(((interpolatedValue - minHeight) / (maxHeight - minHeight)) * 65535);
|
|
pgmData += `${normalizedHeight}\n`;
|
|
}
|
|
}
|
|
|
|
// Create a Blob from the PGM data
|
|
const blob = new Blob([pgmHeader + pgmData], { type: 'image/x-portable-graymap' });
|
|
const url = URL.createObjectURL(blob);
|
|
|
|
// Create a link to download the PGM file
|
|
const link = document.createElement('a');
|
|
link.download = 'heightmap.pgm';
|
|
link.href = url;
|
|
link.click();
|
|
}
|
|
|
|
// Function to export height data as a tab-separated text file with scaling
|
|
function exportText() {
|
|
const scale = parseInt(document.getElementById('scaleSlider').value, 10);
|
|
const resizeTo = size * scale; // New dimensions based on scale
|
|
let textData = '';
|
|
|
|
for (let y = 0; y < resizeTo; y++) {
|
|
for (let x = 0; x < resizeTo; x++) {
|
|
const nx = Math.floor(x / scale);
|
|
const ny = Math.floor(y / scale);
|
|
|
|
// Ensure nx and ny are within bounds
|
|
const nxEdge = (nx < size - 1) ? 1 : 0;
|
|
const nyEdge = (ny < size - 1) ? 1 : 0;
|
|
|
|
const v1 = mHeightDefault[nx + (ny * size)];
|
|
const v2 = mHeightDefault[(nx + nxEdge) + (ny * size)];
|
|
const v3 = mHeightDefault[nx + ((ny + nyEdge) * size)];
|
|
const v4 = mHeightDefault[(nx + nxEdge) + ((ny + nyEdge) * size)];
|
|
|
|
// Bilinear interpolation to get the height value
|
|
const interpolatedValue = bilinearInter(
|
|
Math.floor(x / scale) * scale,
|
|
Math.floor(y / scale) * scale,
|
|
(Math.floor(x / scale) + 1) * scale,
|
|
(Math.floor(y / scale) + 1) * scale,
|
|
x, y, v1, v2, v3, v4);
|
|
|
|
// Add interpolated value to the row array
|
|
textData += interpolatedValue + ' ';
|
|
}
|
|
|
|
textData += '\n'; // Join row with tabs and add a newline
|
|
}
|
|
|
|
for (let z = 0; z < textureNames.length; z++) {
|
|
textData += textureNames[z] + '\n';
|
|
let layer = z + 1;
|
|
switch (layer) {
|
|
case 1:alphaLayer = (!isOffset) ? alphaMap1 : alphaMapOffset1;break;
|
|
case 2:alphaLayer = (!isOffset) ? alphaMap2 : alphaMapOffset2;break;
|
|
case 3:alphaLayer = (!isOffset) ? alphaMap3 : alphaMapOffset3;break;
|
|
case 4:alphaLayer = (!isOffset) ? alphaMap4 : alphaMapOffset4;break;
|
|
case 5:alphaLayer = (!isOffset) ? alphaMap5 : alphaMapOffset5;break;
|
|
case 6:alphaLayer = (!isOffset) ? alphaMap6 : alphaMapOffset6;break;
|
|
}
|
|
|
|
for (let y = 0; y < resizeTo; y++) {
|
|
for (let x = 0; x < resizeTo; x++) {
|
|
const nx = Math.floor(x / scale);
|
|
const ny = Math.floor(y / scale);
|
|
|
|
// Ensure nx and ny are within bounds
|
|
const nxEdge = (nx < size - 1) ? 1 : 0;
|
|
const nyEdge = (ny < size - 1) ? 1 : 0;
|
|
|
|
const v1 = alphaLayer[nx + (ny * size)];
|
|
const v2 = alphaLayer[(nx + nxEdge) + (ny * size)];
|
|
const v3 = alphaLayer[nx + ((ny + nyEdge) * size)];
|
|
const v4 = alphaLayer[(nx + nxEdge) + ((ny + nyEdge) * size)];
|
|
|
|
// Bilinear interpolation to get the height value
|
|
const interpolatedValue = bilinearInter(
|
|
Math.floor(x / scale) * scale,
|
|
Math.floor(y / scale) * scale,
|
|
(Math.floor(x / scale) + 1) * scale,
|
|
(Math.floor(y / scale) + 1) * scale,
|
|
x, y, v1, v2, v3, v4);
|
|
|
|
// Add interpolated value to the row array
|
|
textData += interpolatedValue + ' ';
|
|
}
|
|
|
|
textData += '\n'; // Join row with tabs and add a newline
|
|
}
|
|
}
|
|
|
|
// Create a Blob from the text data
|
|
const blob = new Blob([textData], { type: 'text/plain' });
|
|
const url = URL.createObjectURL(blob);
|
|
|
|
// Create a link to download the text file
|
|
const link = document.createElement('a');
|
|
link.download = 'heightdata.txt';
|
|
link.href = url;
|
|
link.click();
|
|
}
|
|
|
|
// Function to export the terrain mesh as an OBJ file
|
|
function exportObj() {
|
|
if (!map) {
|
|
console.error("No terrain mesh found.");
|
|
return;
|
|
}
|
|
|
|
const vertices = map.getVerticesData(BABYLON.VertexBuffer.PositionKind);
|
|
const indices = map.getIndices();
|
|
const normals = map.getVerticesData(BABYLON.VertexBuffer.NormalKind); // Get the normals data
|
|
|
|
let objData = '';
|
|
|
|
// Add OBJ file header
|
|
objData += '# Exported terrain mesh\n';
|
|
|
|
// Write vertices
|
|
for (let i = 0; i < vertices.length; i += 3) {
|
|
objData += `v ${vertices[i]} ${vertices[i + 1]} ${vertices[i + 2]}\n`;
|
|
}
|
|
|
|
// Write normals
|
|
for (let i = 0; i < normals.length; i += 3) {
|
|
objData += `vn ${normals[i]} ${normals[i + 1]} ${normals[i + 2]}\n`;
|
|
}
|
|
|
|
// Write faces (reverse winding order and include normals)
|
|
for (let i = 0; i < indices.length; i += 3) {
|
|
// Reverse the order of vertices for each face and include normals
|
|
objData += `f ${indices[i + 2] + 1}//${indices[i + 2] + 1} ${indices[i + 1] + 1}//${indices[i + 1] + 1} ${indices[i] + 1}//${indices[i] + 1}\n`;
|
|
}
|
|
|
|
// Create a Blob from the OBJ data
|
|
const blob = new Blob([objData], { type: 'text/plain' });
|
|
const url = URL.createObjectURL(blob);
|
|
|
|
// Create a link to download the OBJ file
|
|
const link = document.createElement('a');
|
|
link.download = 'terrain.obj';
|
|
link.href = url;
|
|
link.click();
|
|
}
|
|
|
|
// Function to export the terrain mesh as an STL file
|
|
function exportStl() {
|
|
if (!map) {
|
|
console.error("No terrain mesh found.");
|
|
return;
|
|
}
|
|
|
|
const vertices = map.getVerticesData(BABYLON.VertexBuffer.PositionKind);
|
|
const indices = map.getIndices();
|
|
let stlData = 'solid terrain\n';
|
|
|
|
// Create facets from the triangles
|
|
for (let i = 0; i < indices.length; i += 3) {
|
|
const v1 = new BABYLON.Vector3(vertices[indices[i] * 3], vertices[indices[i] * 3 + 1], vertices[indices[i] * 3 + 2]);
|
|
const v2 = new BABYLON.Vector3(vertices[indices[i + 1] * 3], vertices[indices[i + 1] * 3 + 1], vertices[indices[i + 1] * 3 + 2]);
|
|
const v3 = new BABYLON.Vector3(vertices[indices[i + 2] * 3], vertices[indices[i + 2] * 3 + 1], vertices[indices[i + 2] * 3 + 2]);
|
|
|
|
// Normal vector for the facet
|
|
const normal = BABYLON.Vector3.Cross(v2.subtract(v1), v3.subtract(v1)).normalize();
|
|
|
|
stlData += `facet normal ${normal.x} ${normal.y} ${normal.z}\n`;
|
|
stlData += 'outer loop\n';
|
|
|
|
// Reverse the vertex order for STL
|
|
stlData += `vertex ${v1.x} ${v1.y} ${v1.z}\n`;
|
|
stlData += `vertex ${v3.x} ${v3.y} ${v3.z}\n`; // Change the order here
|
|
stlData += `vertex ${v2.x} ${v2.y} ${v2.z}\n`; // Change the order here
|
|
|
|
stlData += 'endloop\n';
|
|
stlData += 'endfacet\n';
|
|
}
|
|
|
|
stlData += 'endsolid terrain\n';
|
|
|
|
// Create a Blob from the STL data
|
|
const blob = new Blob([stlData], { type: 'text/plain' });
|
|
const url = URL.createObjectURL(blob);
|
|
|
|
// Create a link to download the STL file
|
|
const link = document.createElement('a');
|
|
link.download = 'terrain.stl';
|
|
link.href = url;
|
|
link.click();
|
|
}
|
|
|
|
function radToDeg(rad) {
|
|
return (rad * 180) / Math.PI;
|
|
}
|
|
|
|
function sNormalFlip(x, y, tempMap) {
|
|
var sqSize = 8;
|
|
if (y % 2 === 0) {
|
|
if (x % 2 === 0) {
|
|
//[0,0]
|
|
var pos1 = [(x * sqSize), (y * sqSize), tempMap[x + (y * 256)]];
|
|
//[1,0]
|
|
var pos2 = [((x + 1) * sqSize), (y * sqSize), tempMap[(x + 1) + (y * 256)]];
|
|
//[1,1]
|
|
var pos3 = [((x + 1) * sqSize), ((y + 1) * sqSize), tempMap[(x + 1) + ((y + 1) * 256)]];
|
|
}
|
|
else {
|
|
//[1,0]
|
|
var pos1 = [((x + 1) * sqSize), (y * sqSize), tempMap[(x + 1) + (y * 256)]];
|
|
//[1,1]
|
|
var pos2 = [((x + 1) * sqSize), ((y + 1) * sqSize), tempMap[(x + 1) + ((y + 1) * 256)]];
|
|
//[0,1]
|
|
var pos3 = [(x * sqSize), ((y + 1) * sqSize), tempMap[x + ((y + 1) * 256)]];
|
|
}
|
|
}
|
|
else {
|
|
if (x % 2 === 1) {
|
|
//[0,0]
|
|
var pos1 = [(x * sqSize), (y * sqSize), tempMap[x + (y * 256)]];
|
|
//[1,0]
|
|
var pos2 = [((x + 1) * sqSize), (y * sqSize), tempMap[(x + 1) + (y * 256)]];
|
|
//[1,1]
|
|
var pos3 = [((x + 1) * sqSize), ((y + 1) * sqSize), tempMap[(x + 1) + ((y + 1) * 256)]];
|
|
}
|
|
else {
|
|
//[1,0]
|
|
var pos1 = [((x + 1) * sqSize), (y * sqSize), tempMap[(x + 1) + (y * 256)]];
|
|
//[1,1]
|
|
var pos2 = [((x + 1) * sqSize), ((y + 1) * sqSize), tempMap[(x + 1) + ((y + 1) * 256)]];
|
|
//[0,1]
|
|
var pos3 = [(x * sqSize), ((y + 1) * sqSize), tempMap[x + ((y + 1) * 256)]];
|
|
}
|
|
}
|
|
var sub2 = [pos3[0] - pos1[0], pos3[1] - pos1[1], pos3[2] - pos1[2]];
|
|
var sub1 = [pos2[0] - pos1[0], pos2[1] - pos1[1], pos2[2] - pos1[2]];
|
|
var cross = [(sub1[1] * sub2[2]) - (sub1[2] * sub2[1]), (sub1[2] * sub2[0]) - (sub1[0] * sub2[2]), (sub1[0] * sub2[1]) - (sub1[1] * sub2[0])];
|
|
|
|
var squared = cross[0] * cross[0] + cross[1] * cross[1] + cross[2] * cross[2];
|
|
if (squared !== 0) {
|
|
var factor = 1 / Math.sqrt(squared);
|
|
cross[0] *= factor;
|
|
cross[1] *= factor;
|
|
cross[2] *= factor;
|
|
} else {
|
|
cross[0] = 0;
|
|
cross[1] = 0;
|
|
cross[2] = 1;
|
|
}
|
|
var up = [0, 0, 1];
|
|
var dot = cross[0] * up[0] + cross[1] * up[1] + cross[2] * up[2];
|
|
var angleRad = Math.acos(dot);
|
|
var angleDeg = radToDeg(angleRad);
|
|
return angleDeg;
|
|
}
|
|
function sNormal(x, y, tempMap) {
|
|
var sqSize = 8;
|
|
if (y % 2 === 0) {
|
|
if (x % 2 === 0) {
|
|
//[0,0]
|
|
var pos1 = [(x * sqSize), (y * sqSize), tempMap[x + (y * 256)]];
|
|
//[0,1]
|
|
var pos2 = [(x * sqSize), ((y + 1) * sqSize), tempMap[x + ((y + 1) * 256)]];
|
|
//[1,1]
|
|
var pos3 = [((x + 1) * sqSize), ((y + 1) * sqSize), tempMap[(x + 1) + ((y + 1) * 256)]];
|
|
}
|
|
else {
|
|
//[0,0]
|
|
var pos1 = [(x * sqSize), (y * sqSize), tempMap[x + (y * 256)]];
|
|
//[0,1]
|
|
var pos2 = [(x * sqSize), ((y + 1) * sqSize), tempMap[x + ((y + 1) * 256)]];
|
|
//[1,0]
|
|
var pos3 = [((x + 1) * sqSize), (y * sqSize), tempMap[(x + 1) + (y * 256)]];
|
|
}
|
|
}
|
|
else {
|
|
if (x % 2 === 1) {
|
|
//[0,0]
|
|
var pos1 = [(x * sqSize), (y * sqSize), tempMap[x + (y * 256)]];
|
|
//[0,1]
|
|
var pos2 = [(x * sqSize), ((y + 1) * sqSize), tempMap[x + ((y + 1) * 256)]];
|
|
//[1,1]
|
|
var pos3 = [((x + 1) * sqSize), ((y + 1) * sqSize), tempMap[(x + 1) + ((y + 1) * 256)]];
|
|
}
|
|
else {
|
|
//[0,0]
|
|
var pos1 = [(x * sqSize), (y * sqSize), tempMap[x + (y * 256)]];
|
|
//[0,1]
|
|
var pos2 = [(x * sqSize), ((y + 1) * sqSize), tempMap[x + ((y + 1) * 256)]];
|
|
//[1,0]
|
|
var pos3 = [((x + 1) * sqSize), (y * sqSize), tempMap[(x + 1) + (y * 256)]];
|
|
}
|
|
}
|
|
var sub1 = [pos3[0] - pos1[0], pos3[1] - pos1[1], pos3[2] - pos1[2]];
|
|
var sub2 = [pos2[0] - pos1[0], pos2[1] - pos1[1], pos2[2] - pos1[2]];
|
|
var cross = [(sub1[1] * sub2[2]) - (sub1[2] * sub2[1]), (sub1[2] * sub2[0]) - (sub1[0] * sub2[2]), (sub1[0] * sub2[1]) - (sub1[1] * sub2[0])];
|
|
|
|
var squared = cross[0] * cross[0] + cross[1] * cross[1] + cross[2] * cross[2];
|
|
if (squared !== 0) {
|
|
var factor = 1 / Math.sqrt(squared);
|
|
cross[0] *= factor;
|
|
cross[1] *= factor;
|
|
cross[2] *= factor;
|
|
} else {
|
|
cross[0] = 0;
|
|
cross[1] = 0;
|
|
cross[2] = 1;
|
|
}
|
|
var up = [0, 0, 1];
|
|
var dot = cross[0] * up[0] + cross[1] * up[1] + cross[2] * up[2];
|
|
var angleRad = Math.acos(dot);
|
|
var angleDeg = radToDeg(angleRad);
|
|
return angleDeg;
|
|
}
|
|
|
|
function spotFix(tempMap) {
|
|
var mrx = 0;
|
|
var nextPass = 0;
|
|
var fixCount = 0;
|
|
var fixCountM = 0;
|
|
var fixCountF = 0;
|
|
for (let pass = 1; pass < 100; pass++) {
|
|
nextPass = 0;
|
|
fixCount = 0;
|
|
mrx = 0;
|
|
fixCountM = 0;
|
|
fixCountF = 0;
|
|
var minAng = 1.5;// first pas adjust verts by 1 degree then on other passes loose the tollerance to reduce passes
|
|
for (let y = 0; y < 255; y++) {
|
|
for (let x = 0; x < 255; x++) {
|
|
var he = tempMap[x + (y * 256)];
|
|
var terAng = sNormal(x, y, tempMap);
|
|
var terAngFlip = sNormalFlip(x, y, tempMap);
|
|
mrx = x + y;
|
|
if (terAng <= minAng || terAngFlip <= minAng) {
|
|
nextPass = 1;
|
|
fixCount++;
|
|
if (terAng <= minAng) {
|
|
fixCountM++;
|
|
}
|
|
if (terAngFlip <= minAng) {
|
|
fixCountF++;
|
|
}
|
|
for (let z = 1; z < 200; z++) {
|
|
if (mrx % 2 === 0) {
|
|
tempMap[x + (y * 256)] = he + (0.1 * z);
|
|
}
|
|
else {
|
|
tempMap[x + (y * 256)] = he - (0.1 * z);
|
|
if (sNormalFlip(x, y, tempMap) <= minAng) {
|
|
tempMap[(x + 1) + ((y) * 256)] = he + (0.1 * z);
|
|
}
|
|
}
|
|
var rx = sNormal(x, y, tempMap);
|
|
var tx = sNormalFlip(x, y, tempMap);
|
|
if (rx > 1 && tx > 1) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (nextPass === 0) {
|
|
console.log('pass count done= ' + pass);
|
|
break;
|
|
}
|
|
else {
|
|
console.log('pass count =' + pass + ' fix count ' + fixCount + ' mcount ' + fixCountM + ' fcount ' + fixCountF);
|
|
}
|
|
}
|
|
return tempMap;
|
|
}
|
|
|
|
document.getElementById('LayerBtn1').addEventListener('click', function () {
|
|
const scale = parseInt(document.getElementById('scaleSlider').value, 10);
|
|
exportAlphaMap(1, scale);
|
|
})
|
|
document.getElementById('LayerBtn2').addEventListener('click', function () {
|
|
const scale = parseInt(document.getElementById('scaleSlider').value, 10);
|
|
exportAlphaMap(2, scale);
|
|
})
|
|
document.getElementById('LayerBtn3').addEventListener('click', function () {
|
|
const scale = parseInt(document.getElementById('scaleSlider').value, 10);
|
|
exportAlphaMap(3, scale);
|
|
})
|
|
document.getElementById('LayerBtn4').addEventListener('click', function () {
|
|
const scale = parseInt(document.getElementById('scaleSlider').value, 10);
|
|
exportAlphaMap(4, scale);
|
|
})
|
|
document.getElementById('LayerBtn5').addEventListener('click', function () {
|
|
const scale = parseInt(document.getElementById('scaleSlider').value, 10);
|
|
exportAlphaMap(5, scale);
|
|
})
|
|
document.getElementById('LayerBtn6').addEventListener('click', function () {
|
|
const scale = parseInt(document.getElementById('scaleSlider').value, 10);
|
|
exportAlphaMap(6, scale);
|
|
})
|
|
document.getElementById('LayerBtn7').addEventListener('click', function () {
|
|
const scale = parseInt(document.getElementById('scaleSlider').value, 10);
|
|
exportDeadMapImage(7, scale);
|
|
})
|
|
|
|
document.getElementById('exportTerFixButton').addEventListener('click', exportTerFlatSpot);
|
|
document.getElementById('exportTerFixButtonCone').addEventListener('click', exportTerFlatSpotCone);
|
|
document.getElementById('exportTerButton').addEventListener('click', exportTer);
|
|
// Button to export terrain mesh as STL
|
|
document.getElementById('exportStlButton').addEventListener('click', exportStl);
|
|
// Button to export terrain mesh as OBJ
|
|
document.getElementById('exportObjButton').addEventListener('click', exportObj);
|
|
|
|
// Button to export height data as text
|
|
document.getElementById('exportTextButton').addEventListener('click', exportText);
|
|
|
|
// Button to export the PGM image
|
|
document.getElementById('exportPGMButton').addEventListener('click', exportPGM);
|
|
// Button to export the grayscale image as PNG
|
|
document.getElementById('exportButton').addEventListener('click', exportScaledImage);
|
|
|
|
// Update the scale value display when slider changes
|
|
document.getElementById('scaleSlider').addEventListener('input', function () {
|
|
const scaleValue = document.getElementById('scaleValue');
|
|
scaleValue.textContent = this.value;
|
|
});
|
|
|
|
// Listen for file input change to load the .ter file
|
|
document.getElementById('fileInput').addEventListener('change', function (event) {
|
|
const file = event.target.files[0];
|
|
if (file) {
|
|
console.log(`Loading file: ${file.name}`);
|
|
// Reset the terrain first
|
|
mHeight = [];
|
|
if (map) {
|
|
map.dispose(); // Dispose of the previous terrain mesh
|
|
map = null; // Reset the reference
|
|
}
|
|
loadTerFile(file);
|
|
} else {
|
|
console.error("No file selected.");
|
|
}
|
|
});
|
|
|
|
// Button to toggle between wireframe and shaded view
|
|
// Create a flat shading material
|
|
|
|
// Button to toggle between wireframe and shaded view
|
|
document.getElementById('toggleViewButton').addEventListener('click', function () {
|
|
if (map) { // Ensure the map (terrain mesh) exists
|
|
const material = map.material;
|
|
const isWireframe = material.wireframe;
|
|
|
|
// Toggle the wireframe property
|
|
material.wireframe = !isWireframe;
|
|
|
|
// Update lights and emissive color based on the current view
|
|
if (material.wireframe) {
|
|
// In wireframe mode, disable lights and change emissive color
|
|
scene.lights.forEach(light => light.setEnabled(false));
|
|
material.emissiveColor = new BABYLON.Color3(0.6, 0.6, 0.6); // Set emissive color to white in wireframe mode
|
|
this.textContent = 'Switch to Shaded View'; // Update button text
|
|
} else {
|
|
// In shaded mode, enable lights and reset emissive color
|
|
scene.lights.forEach(light => light.setEnabled(true));
|
|
material.emissiveColor = new BABYLON.Color3(0, 0, 0); // Reset emissive color to off in shaded mode
|
|
this.textContent = 'Switch to Wireframe View'; // Update button text
|
|
}
|
|
} else {
|
|
console.error("Terrain mesh not found.");
|
|
}
|
|
});
|
|
|
|
// Render loop
|
|
engine.runRenderLoop(function () {
|
|
scene.render();
|
|
});
|
|
|
|
// Handle window resizing
|
|
window.addEventListener('resize', function () {
|
|
engine.resize();
|
|
});
|
|
|
|
//scene.debugLayer.show({
|
|
// embedMode: true, // Display as an embedded UI
|
|
// overlay: true, // Display on top of the canvas
|
|
// globalRoot: document.body, // Attach to the body or another element
|
|
// position: {
|
|
// top: 0, // Position at the top
|
|
// right: 0 // Align to the right side
|
|
// }
|
|
//});
|
|
|
|
|
|
|
|
let isDragging = false;
|
|
let dragStart = { x: 0, y: 0 };
|
|
let imgoffset = { x: 0, y: 0 };
|
|
let greyscaleImageData;
|
|
var updateTimeOut = null;
|
|
function enableGreyscaleDragging() {
|
|
const greyscaleCanvas = document.getElementById('greyscaleCanvas');
|
|
const context = greyscaleCanvas.getContext('2d');
|
|
|
|
greyscaleCanvas.addEventListener('mousedown', (event) => {
|
|
isDragging = true;
|
|
dragStart = { x: event.offsetX, y: event.offsetY };
|
|
});
|
|
|
|
greyscaleCanvas.addEventListener('mousemove', (event) => {
|
|
if (isDragging) {
|
|
const dx = event.offsetX - dragStart.x;
|
|
const dy = event.offsetY - dragStart.y;
|
|
imgoffset.x = (imgoffset.x + dx) % size; // Wrap horizontally
|
|
imgoffset.y = (imgoffset.y + dy) % size; // Wrap vertically
|
|
dragStart = { x: event.offsetX, y: event.offsetY };
|
|
|
|
redrawGreyscaleImage(context);
|
|
updateHeightmapWithOffset();
|
|
createTerrain2();
|
|
generateDeadMapImage(7);
|
|
}
|
|
});
|
|
|
|
greyscaleCanvas.addEventListener('mouseup', () => {
|
|
isDragging = false;
|
|
updateHeightAlphamapWithOffset();
|
|
});
|
|
|
|
greyscaleCanvas.addEventListener('mouseleave', () => {
|
|
isDragging = false;
|
|
updateHeightAlphamapWithOffset();
|
|
});
|
|
}
|
|
|
|
function redrawGreyscaleImage(context) {
|
|
if (!greyscaleImageData) return;
|
|
const tempCanvas = document.createElement('canvas');
|
|
const tempContext = tempCanvas.getContext('2d');
|
|
tempCanvas.width = size;
|
|
tempCanvas.height = size;
|
|
tempContext.putImageData(greyscaleImageData, 0, 0);
|
|
|
|
context.clearRect(0, 0, greyscaleCanvas.width, greyscaleCanvas.height);
|
|
|
|
// Draw the image in a wrapping/looping manner
|
|
for (let y = -size; y <= size; y += size) {
|
|
for (let x = -size; x <= size; x += size) {
|
|
context.drawImage(
|
|
tempCanvas,
|
|
(x + imgoffset.x) % size,
|
|
(y + imgoffset.y) % size,
|
|
size,
|
|
size
|
|
);
|
|
}
|
|
}
|
|
}
|
|
function updateHeightmapWithOffset() {
|
|
// Normalize the offset to avoid cumulative errors
|
|
imgoffset.x = ((imgoffset.x % size) + size) % size;
|
|
imgoffset.y = ((imgoffset.y % size) + size) % size;
|
|
|
|
for (let y = 0; y < size; y++) {
|
|
for (let x = 0; x < size; x++) {
|
|
// Calculate source indices with normalized wrapping
|
|
const srcX = (x - imgoffset.x + size) % size;
|
|
const srcY = (y - imgoffset.y + size) % size; // Subtract offset.y for inverted up/down movement
|
|
|
|
const srcIndex = srcY * size + srcX;
|
|
const destIndex = y * size + x;
|
|
|
|
// Update the new height data
|
|
mHeightOffset[destIndex] = mHeight[srcIndex];
|
|
}
|
|
}
|
|
}
|
|
|
|
function updateHeightAlphamapWithOffset() {
|
|
for(let z = 0; z < textureNames.length; z++){
|
|
let layer = z + 1;
|
|
// Normalize the offset to avoid cumulative errors
|
|
imgoffset.x = ((imgoffset.x % size) + size) % size;
|
|
imgoffset.y = ((imgoffset.y % size) + size) % size;
|
|
|
|
for (let y = 0; y < size; y++) {
|
|
for (let x = 0; x < size; x++) {
|
|
// Calculate source indices with normalized wrapping
|
|
const srcX = (x - imgoffset.x + size) % size;
|
|
const srcY = (y - imgoffset.y + size) % size; // Subtract offset.y for inverted up/down movement
|
|
|
|
const srcIndex = srcY * size + srcX;
|
|
const destIndex = y * size + x;
|
|
// Update the new alpha data
|
|
switch (layer) {
|
|
case 1:alphaMapOffset1[destIndex] = alphaMap1[srcIndex];break;
|
|
case 2:alphaMapOffset2[destIndex] = alphaMap2[srcIndex];break;
|
|
case 3:alphaMapOffset3[destIndex] = alphaMap3[srcIndex];break;
|
|
case 4:alphaMapOffset4[destIndex] = alphaMap4[srcIndex];break;
|
|
case 5:alphaMapOffset5[destIndex] = alphaMap5[srcIndex];break;
|
|
case 6:alphaMapOffset6[destIndex] = alphaMap6[srcIndex];break;
|
|
}
|
|
}
|
|
}
|
|
switch (layer) {
|
|
case 1:generateAlphaMapImage(alphaMapOffset1, layer);break;
|
|
case 2:generateAlphaMapImage(alphaMapOffset2, layer);break;
|
|
case 3:generateAlphaMapImage(alphaMapOffset3, layer);break;
|
|
case 4:generateAlphaMapImage(alphaMapOffset4, layer);break;
|
|
case 5:generateAlphaMapImage(alphaMapOffset5, layer);break;
|
|
case 6:generateAlphaMapImage(alphaMapOffset6, layer);break;
|
|
}
|
|
}
|
|
}
|
|
function bilinearInter(x1, y1, x2, y2, x, y, v1, v2, v3, v4) {
|
|
let w11 = (((x2 - x) * (y2 - y)) / ((x2 - x1) * (y2 - y1))) * v1; // x1 y1
|
|
let w21 = (((x - x1) * (y2 - y)) / ((x2 - x1) * (y2 - y1))) * v2; // x2 y1
|
|
let w12 = (((x2 - x) * (y - y1)) / ((x2 - x1) * (y2 - y1))) * v3; // x1 y2
|
|
let w22 = (((x - x1) * (y - y1)) / ((x2 - x1) * (y2 - y1))) * v4; // x2 y2
|
|
return w11 + w21 + w12 + w22;
|
|
}
|
|
</script>
|
|
</body>
|
|
|
|
</html>
|