/**
* @name Voxengine
* @class ボクセルデータを管理/編集するための機能
* @property {Number} chunkGrid ボクセルデータを内包するチャンク空間
* @property {Number} voxCount Voxengineを通して編集された実体のあるボクセル数
*/
var Voxengine = pc.createScript('voxengine');
Voxengine.attributes.add('chunkEntity', {
type: 'entity',
title: 'Vox Chunk',
});
Voxengine.attributes.add("chunkSize", {
type: "number",
title: "Chunk Size",
});
Voxengine.prototype.initialize = function() {
voxengine = this;
this.chunkGrid = {};
this.voxCount = 0;
};
// ---------------------------------------------
// utility methods
// ---------------------------------------------
Voxengine.prototype.convertToChunkIdx = function(v) {
var x = Math.floor(v.x / this.chunkSize);
var y = Math.floor(v.y / this.chunkSize);
var z = Math.floor(v.z / this.chunkSize);
return new pc.Vec3(x, y, z);
};
Voxengine.prototype.convertToChunkPosition = function(cIdx) {
var p = new pc.Vec3();
p.copy(cIdx);
p.x = Math.round(p.x);
p.y = Math.round(p.y);
p.z = Math.round(p.z);
p.scale(this.chunkSize);
return p;
};
Voxengine.prototype.convertToGlobalVoxIdx = function(v) {
var x = Math.floor(v.x) + 0.5;
var y = Math.floor(v.y) + 0.5;
var z = Math.floor(v.z) + 0.5;
return new pc.Vec3(x, y, z);
};
Voxengine.prototype.getVoxIdxsInSpace = function(start, end) {
var voxIdxs = [];
var startIdx = this.convertToGlobalVoxIdx(start);
var endIdx = this.convertToGlobalVoxIdx(end);
var t = voxutil.minMaxVec3(startIdx, endIdx);
var min = t.min;
var max = t.max;
for(var x = min.x; x <= max.x; ++x) {
for(var y = min.y; y <= max.y; ++y) {
for(var z = min.z; z <= max.z; ++z) {
voxIdxs.push(new pc.Vec3(x, y, z));
}
}
}
return voxIdxs;
};
// ---------------------------------------------
// methods to edit vox
// ---------------------------------------------
Voxengine.prototype.putVoxWithVec3 = function(v, c) {
var cIdx = this.convertToChunkIdx(v);
if(!this.checkChunk(cIdx)) {
this.createChunk(cIdx);
}
var chunk = this.getChunk(cIdx);
var cPosition = this.convertToChunkPosition(cIdx);
var p = new pc.Vec3(v.x, v.y, v.z);
p.sub(cPosition);
if(chunk.putVoxWithVec3(p, c)) {
this.voxCount++;
return true;
} else {
return false;
}
};
Voxengine.prototype.removeVoxWithVec3 = function(v) {
var cIdx = this.convertToChunkIdx(v);
if(!this.checkChunk(cIdx)) {
return false;
}
var chunk = this.getChunk(cIdx);
var cPosition = this.convertToChunkPosition(cIdx);
var p = new pc.Vec3(v.x, v.y, v.z);
p.sub(cPosition);
if(chunk.removeVoxWithVec3(p)) {
this.voxCount--;
return true;
} else {
return false;
}
};
Voxengine.prototype.getVoxWithVec3 = function(v) {
var cIdx = this.convertToChunkIdx(v);
if(!this.checkChunk(cIdx)) {
return null;
}
var chunk = this.getChunk(cIdx);
var cPosition = this.convertToChunkPosition(cIdx);
var p = new pc.Vec3(v.x, v.y, v.z);
p.sub(cPosition);
return chunk.getVoxWithVec3(p);
};
Voxengine.prototype.setVoxColorWithVec3 = function(v, color) {
var cIdx = this.convertToChunkIdx(v);
if(!this.checkChunk(cIdx)) {
return false;
}
var chunk = this.getChunk(cIdx);
var cPosition = this.convertToChunkPosition(cIdx);
var p = new pc.Vec3(v.x, v.y, v.z);
p.sub(cPosition);
return chunk.setVoxColorWithVec3(p, color);
};
// ---------------------------------------------
// methods to edit vox
// ---------------------------------------------
Voxengine.prototype.setVoxesAtChunk = function(cIdx, visibles, colors) {
var chunk = this.getChunk(cIdx);
if(chunk === null) {
chunk = this.createChunk(cIdx);
}
chunk.setVoxes(visibles, colors);
};
// ---------------------------------------------
// methods to manage chunks
// ---------------------------------------------
Voxengine.prototype.checkChunk = function(cIdx) {
return this.chunkGrid[cIdx] !== void 0;
};
Voxengine.prototype.createChunk = function(cIdx) {
if(!this.checkChunk(cIdx)) {
// チャンクが未作成
var newChunk = this.chunkEntity.clone();
newChunk.enabled = true;
this.entity.addChild(newChunk);
var p = this.convertToChunkPosition(cIdx);
var voxChunk = newChunk.script.voxchunk;
voxChunk.initializeChunk(cIdx, this, this.chunkSize, p);
this.chunkGrid[cIdx] = voxChunk;
return voxChunk;
}
return null;
};
Voxengine.prototype.getChunk = function(cIdx) {
return this.checkChunk(cIdx) ? this.chunkGrid[cIdx] : null;
};
Voxengine.prototype.haveChunkRefresh = function(cIdx) {
// チャンクのタイミングで更新
var chunk = this.getChunk(cIdx);
if(chunk !== null) {
chunk.dirty = true;
}
};
Voxengine.prototype.makeChunkRefresh = function(cIdx) {
// 強制的に更新
var chunk = this.getChunk(cIdx);
if(chunk !== null) {
chunk.updateChunk();
}
};
// ---------------------------------------------
// methods to inport/export a vox data
// ---------------------------------------------
/**
* 津田フォーマットでボクセルを出力
* @return {string} 津田フォーマットテキスト
*/
Voxengine.prototype.exportOriginalFormat = function() {
// format = (x,y,z,(r,g,b,a,)"a")*
var exp = "";
var cIdx;
var ret;
var color = null;
for(cIdx in this.chunkGrid) {
ret = this.chunkGrid[cIdx].exportOriginalFormat(color);
exp += ret[0];
color = ret[1];
exp += "a";
}
return exp.slice(0, -1);
};
// 津田フォーマットテキストを入力
Voxengine.prototype.importOriginalFormat = function(imp) {
// format = (x,y,z,(r,g,b,a,)"a")*
var impVoxes = imp.split("a");
var i;
var position = new pc.Vec3(0, 0, 0);
var color = new pc.Color(1, 1, 1, 1);
for(i = 0; i < impVoxes.length; ++i){
var elems = impVoxes[i].split(",");
position.x = parseInt(elems[0]);
position.y = parseInt(elems[1]);
position.z = parseInt(elems[2]);
if(elems.length > 3) {
color.r = (parseFloat(elems[3]) / 255);
color.g = (parseFloat(elems[4]) / 255);
color.b = (parseFloat(elems[5]) / 255);
color.a = parseFloat(elems[6]) > 0.99 ? 1 : 0.5;
}
this.putVoxWithVec3(position, color);
}
};
// 擬似的なobjフォーマットテキストを出力
Voxengine.prototype.exportPesudeObjFormat = function() {
var meshs = [];
var colors = [];
for(var cIdx in this.chunkGrid) {
ret = this.chunkGrid[cIdx].exportPesudeObjFormat();
Array.prototype.push.apply(meshs, ret.meshs);
Array.prototype.push.apply(colors, ret.colors);
}
return {meshs:meshs, colors:colors};
};
// objフォーマットテキストを出力
Voxengine.prototype.exportObjFormat = function(name) {
const meshData = this.exportMargedMeshData();
const tMeshData = this.exportMargedTransparentMeshData();
// 2つをマージ
// オーバーフロー対策
const push = Array.prototype.push;
let p = []; // push.apply(p, meshData.p); push.apply(p, tMeshData.p);
for(let i = 0; i < meshData.p.length; ++i) p.push(meshData.p[i]);
for(let i = 0; i < tMeshData.p.length; ++i) p.push(tMeshData.p[i]);
let n = []; // push.apply(n, meshData.n); push.apply(n, tMeshData.n);
for(let i = 0; i < meshData.n.length; ++i) n.push(meshData.n[i]);
for(let i = 0; i < tMeshData.n.length; ++i) n.push(tMeshData.n[i]);
let c = []; // push.apply(c, meshData.c); push.apply(c, tMeshData.c);
for(let i = 0; i < meshData.c.length; ++i) c.push(meshData.c[i]);
for(let i = 0; i < tMeshData.c.length; ++i) c.push(tMeshData.c[i]);
// インデックスは透過側にオフセットを付ける
const vOffset = meshData.p.length / 3;
const idxs = meshData.i; // 非透過メッシュインデックス
const oIdxs = []; // 透過メッシュインデックス。オフセット有り。
for(let j = 0; j < tMeshData.i.length; ++j) {
oIdxs.push(tMeshData.i[j] + vOffset);
}
// 頂点カラーをuv座標に変換
const t = [];
const tex = new Voxtexture(c);
const color = [0, 0, 0, 0];
for(let i = 0; i < c.length; i += 4) {
color[0] = c[i];
color[1] = c[i + 1];
color[2] = c[i + 2];
color[3] = c[i + 3];
let uv = tex.color2UV(color);
t.push(uv.x, uv.y);
}
// 透過/非透過の面情報を作成
const faces = [];
const tFaces = [];
for(let i = 0; i < idxs.length; ++i) {
faces.push([
idxs[i] + 1, // position
idxs[i] + 1, // texcoord
idxs[i] + 1 // normal
]);
}
for(let i = 0; i < oIdxs.length; ++i) {
tFaces.push([
oIdxs[i] + 1, // position
oIdxs[i] + 1, // texcoord
oIdxs[i] + 1 // normal
]);
}
// objフォーマットテキストに変換
const objText = this.makeObjFormatText(name, p, t, n, faces, tFaces);
// mtlフォーマットテキスト出力
const mtlText = this.makeMTLFormatText(name + ".png");
// pngフォーマットテキスト出力
const pngData = tex.exportPngFormat();
// テスト
// this.testDownload(name, objText, mtlText, pngData);
return {obj:objText, mtl:mtlText, png:pngData};
};
Voxengine.prototype.testDownload = function(name, obj, mtl, png) {
let dl = document.createElement("a");
let blob;
// obj
blob = new Blob([obj], {type: "text/obj"});
dl.download = name + ".obj";
dl.href = window.URL.createObjectURL(blob);
dl.click();
// mtl
blob = new Blob([mtl], {type: "text/mtl"});
dl.download = name + ".mtl";
dl.href = window.URL.createObjectURL(blob);
dl.click();
// png
let buffer = new Uint8Array(png.length);
// Uint8Array ビューに 1 バイトずつ値を埋める
for (let i = 0; i < png.length; i++) {
buffer[i] = png.charCodeAt(i);
}
// Uint8Array ビューのバッファーを抜き出し、それを元に Blob を作る
blob = new Blob([buffer.buffer], {type: "image/png"});
// 試しにpngをダウンロード
dl.download = name + ".png";
dl.href = window.URL.createObjectURL(blob);
dl.click();
};
Voxengine.prototype.makeObjFormatText = function(name, p, t, n, f, tf) {
let text = "";
text = "# powerd by VOXELCANVAS\n";
text += "# https://voxelcanvas.me\n\n";
text += "# Author ryotaro,seiro\n";
text += "# https://utautattaro.com/\n";
text += "# https://seir.online/\n";
text += "\n\n\n";
text += "mtllib " + name + ".mtl\n";
for(let i = 0; i < p.length; i += 3) {
text += "v " + p[i] + " " + p[i + 1] + " " + p[i + 2] + "\n";
}
for(let i = 0; i < t.length; i += 2) {
text += "vt " + t[i] + " " + t[i + 1] + "\n";
}
for(let i = 0; i < n.length; i += 3) {
text += "vn " + n[i] + " " + n[i + 1] + " " + n[i + 2] + "\n";
}
// 非透過
text += "\n\ng mesh\nusemtl mat0\n";
for(let i = 0; i < f.length; i += 3) {
let temp = f[i];
text += "f " + temp[0] + "/" + temp[1] + "/" + temp[2];
text += " ";
temp = f[i + 1];
text += temp[0] + "/" + temp[1] + "/" + temp[2];
text += " ";
temp = f[i + 2];
text += temp[0] + "/" + temp[1] + "/" + temp[2] + "\n";
}
// 透過
text += "\n\ng transparent \nusemtl mat1\n";
for(let i = 0; i < tf.length; i += 3) {
let temp = tf[i];
text += "f " + temp[0] + "/" + temp[1] + "/" + temp[2];
text += " ";
temp = tf[i + 1];
text += temp[0] + "/" + temp[1] + "/" + temp[2];
text += " ";
temp = tf[i + 2];
text += temp[0] + "/" + temp[1] + "/" + temp[2] + "\n";
}
return text;
};
Voxengine.prototype.makeMTLFormatText = function(png) {
let text = "";
text += "# VOXELCANVAS @ voxelover\n\n";
// 非透過
text += "newmtl mat0\n";
text += "illum 1\n";
text += "Ka 0.000 0.000 0.000\n";
text += "Kd 1.000 1.000 1.000\n";
text += "Ks 0.000 0.000 0.000\n";
text += "d 1.0\n";
text += "map_Kd " + png + "\n\n";
// 透過
text += "newmtl mat1\n";
text += "illum 1\n";
text += "Ka 0.000 0.000 0.000\n";
text += "Kd 1.000 1.000 1.000\n";
text += "Ks 0.000 0.000 0.000\n";
text += "d 0.5\n";
text += "map_Kd " + png + "\n";
return text;
};
// チャンク毎の非透過メッシュデータを統合したメッシュデータを出力
Voxengine.prototype.exportMargedMeshData = function() {
let vOffset = 0;
const positions = [];
const normals = [];
const colors = [];
const indices = [];
const push = Array.prototype.push;
for(let cIdx in this.chunkGrid) {
const meshData = this.chunkGrid[cIdx].exportMeshData();
push.apply(positions, meshData.p);
push.apply(normals, meshData.n);
push.apply(colors, meshData.c);
for(let j = 0; j < meshData.i.length; ++j) {
indices.push(meshData.i[j] + vOffset);
}
vOffset += meshData.p.length / 3;
}
return {p:positions, n:normals, c:colors, i:indices};
};
// チャンク毎の透過メッシュデータを統合したメッシュデータを出力
Voxengine.prototype.exportMargedTransparentMeshData = function() {
let vOffset = 0;
const positions = [];
const normals = [];
const colors = [];
const indices = [];
const push = Array.prototype.push;
for(let cIdx in this.chunkGrid) {
const tMeshData = this.chunkGrid[cIdx].exportTransparentMeshData();
push.apply(positions, tMeshData.p);
push.apply(normals, tMeshData.n);
push.apply(colors, tMeshData.c);
for(let j = 0; j < tMeshData.i.length; ++j) {
indices.push(tMeshData.i[j] + vOffset);
}
vOffset += tMeshData.p.length / 3;
}
return {p:positions, n:normals, c:colors, i:indices};
};