import { Utils, getTransformOffset } from "../common/Utils";
import { Component, MathUtils } from "@uino/thing";
const __ = {
private: Symbol('private'),
}
const WallType = {
ModelWall: "ModelWall",
TextureWall: "TextureWall"
}
let defCenter = [0, 0, 0];
let defHalfSize = [0, 0, 0];
let defRotation = [0, 0, 0];
let modelSizeMap = {};
/**
* 墙组件,用于生成墙体
* @extends THING.Component
* @public
* @example
* // 模拟数据
* const points = [[0, 0], [2, 0], [2, 2], [0, 2]]
* const materialResource = { map: './images/camera.png' }
* // 创建对象作为墙体
* const wall = new THING.Object3D({
* tags: ['BuildingElement', 'Wall']
* });
* // 实例化平面组件
* const wallComponent = new THING.WallComponent();
* wall.addComponent(wallComponent, 'wallComponent');
* wallComponent.load({
* type: 'TextureWall',
* walls: [
* {
* points: [...points[0], ...points[1]],
* leftMaterial: materialResource,
* rightMaterial: materialResource,
* edgeMaterial: materialResource
* },
* {
* points: [...points[1], ...points[2]],
* leftMaterial: materialResource,
* rightMaterial: materialResource,
* edgeMaterial: materialResource
* }, {
* points: [...points[2], ...points[3]],
* leftMaterial: materialResource,
* rightMaterial: materialResource,
* edgeMaterial: materialResource
* }, {
* points: [...points[3], ...points[0]],
* leftMaterial: materialResource,
* rightMaterial: materialResource,
* edgeMaterial: materialResource
* }
* ],
* }).then(() => {
* console.log('墙体加载完成');
* })
*/
class WallComponent extends Component {
constructor(param) {
super(param);
let _private = this[__.private] = {};
_private.data = null;
_private.dataType = null;
_private.pickIds = null;
/**
* Sets the wall data and options.
* @param {Object} data - The wall data.
* @param {Object} options - The options.
*/
_private.setData = (data, options) => {
if (!data) {
return;
}
_private.toolkit = options;
_private.data = data;
_private.dataType = data.type;
_private.pickIds = data.pickIds;
_private.walls = data.walls;
_private.materialExtendOptions = {
defineUsePickId: !!_private.pickIds
};
if (data.type === WallType.ModelWall) {
_private._parseModelWalls(data.walls);
}
}
/**
* Returns the wall data.
* @returns {Object} The wall data.
*/
_private.getData = (options) => {
let exportData = {
type: _private.dataType
};
if (_private.pickIds) {
exportData.pickIds = _private.pickIds;
}
if (_private.walls) {
// 浅拷贝wall,防止多次导出时 数据源被修改
const walls = [];
_private.walls.forEach(wall => {
walls.push(Object.assign({}, wall));
});
exportData.walls = walls;
}
return exportData;
}
/**
* Loads the wall object.
* @param {Object} wallObject - The wall object.
* @returns {Promise} A promise that resolves when the loading is complete.
*/
_private.load = (wallObject) => {
if (_private.dataType === WallType.ModelWall) {
return _private._loadModelWall(wallObject);
}
if (_private.dataType === WallType.TextureWall) {
return _private._loadTextureWall(wallObject);
}
return Promise.resolve();
}
_private._getParserManager = () => {
if (!_private._parserManager) {
_private._parserManager = Utils.createObject('ParserManager');
}
return _private._parserManager;
}
//#region load texture wall
_private._loadTextureWall = (wallObject) => {
let promiseArr = [];
let newWalls = [];
_private.walls.forEach(element => {
if (element.visible !== false) {
element.height = Utils.parseNumber(element.height, 3);
element.thickness = Utils.parseNumber(element.thickness, 0.25);
element = Object.assign({}, element);
element.leftMaterial = _private._loadMaterialResource(element.leftMaterial, promiseArr);
element.rightMaterial = _private._loadMaterialResource(element.rightMaterial, promiseArr);
element.edgeMaterial = _private._loadMaterialResource(element.edgeMaterial, promiseArr);
newWalls.push(element);
}
});
return Promise.all(promiseArr).then(
() => {
_private._parseWall(wallObject, newWalls)
},
() => {
_private._parseWall(wallObject, newWalls)
}
)
}
_private._loadMaterialResource = (data, promiseArr) => {
if (!data) {
return null
}
const { material, promise } = this.app._materialResourceLoader.createMaterialResource(data);
promiseArr.push(promise);
return material;
}
_private._parseWall = (wallObject, newWalls) => {
if (wallObject.destroyed) {
return;
}
const wallNode = _private._getParserManager().parseWall(newWalls, { pickIds: _private.pickIds });
wallObject.body.setNode(wallNode);
}
//#endregion
//#region load model wall
_private._parseModelWalls = (walls) => {
walls.forEach(wallData => {
const modelIndex = wallData['model'];
if (typeof modelIndex !== 'number') {
return;
}
if (_private.toolkit.getURIByIndex) {
wallData['model'] = _private.toolkit.resolveURL(_private.toolkit.getURIByIndex(modelIndex))
}
if (_private.toolkit.getModelDataByIndex) {
const _data = _private.toolkit.getModelDataByIndex(modelIndex);
wallData['model'] = _data.url;
}
});
}
_private._loadModelWall = async (wallObject) => {
const walls = _private.walls;
let mergeDataMap = {};
let pickidMap = {};
for (let i = 0; i < walls.length; i++) {
const wallData = walls[i];
const modelUrl = wallData['model'];
if (wallData.visible === false || !modelUrl) {
continue;
}
// Get size of the model
let modelSize = modelSizeMap[modelUrl];
if (!modelSize) {
modelSize = await _private._getModelSize(modelUrl, wallObject);
}
modelSizeMap[modelUrl] = modelSize;
// Generate matrix data of the wall
let transfromData = _private._generateModelWallTransfrom(wallData, modelSize);
// Generate pick id of the wall
if (_private.pickIds) {
let pickId = _private.pickIds[i];
let tempPickids = [];
transfromData.forEach(element => {
tempPickids.push(pickId);
});
pickidMap[modelUrl] = pickidMap[modelUrl] ? pickidMap[modelUrl].concat(tempPickids) : tempPickids;
}
mergeDataMap[modelUrl] = mergeDataMap[modelUrl] ? mergeDataMap[modelUrl].concat(transfromData) : transfromData;
}
// Create wall
const keys = Object.keys(mergeDataMap);
let promises = [];
for (let i = 0; i < keys.length; i++) {
const url = keys[i];
const transfromData = mergeDataMap[url];
promises[i] = _private._mergeCreate(url, transfromData, pickidMap[url], wallObject);
}
return Promise.all(promises);
}
_private._generateModelWallTransfrom = (wallData, modelSize) => {
let result = [];
// 1. Ready param
const points = wallData.points;
const startPosition = [points[0], 0, points[1] + getTransformOffset()];
const endPosition = [points[2] + getTransformOffset(), 0, points[3]];
const holes = wallData['holes'];
const height = wallData['height'];
const thickness = wallData['thickness'];
// 2. Generate lines data
const lines = _private._generateLines(startPosition, endPosition, holes);
// 3. Calc public transform property
const forward = MathUtils.normalizeVector(MathUtils.subVector(endPosition, startPosition));
const quat = MathUtils.getQuatFromUnitVectors([-1, 0, 0], forward);
const euler = MathUtils.getEulerFromQuat(quat);
const angles = [MathUtils.radToDeg(euler[0]), MathUtils.radToDeg(euler[1]), MathUtils.radToDeg(euler[2])];
const modelDistanceHalf = MathUtils.scaleVector(forward.concat(), modelSize[0] / 2);
const modelDistance = MathUtils.scaleVector(forward.concat(), modelSize[0]);
const scaleY = height == 0 ? 0.001 : height / modelSize[1];
const scaleZ = thickness == 0 ? 0.001 : thickness / modelSize[2];
// 4. Generate transform data
for (let j = 0; j < lines.length; j++) {
const line = lines[j];
// calc create count
let count = MathUtils.getDistance(line.start, line.end) / modelSize[0];
const decimal = count % 1; // get decimals
count = Math.floor(count); // get integer
count += 1;
// create
if (count == 1) {
const position = MathUtils.getPointOnLine(line.start, line.end, 0.5);
const mat4 = MathUtils.createMat4(
[position[0], 0, position[2]],
angles,
[decimal, scaleY, scaleZ]
);
result.push(mat4);
}
else {
let position;
let scaleX;
for (let k = 0; k < count; k++) {
let point = MathUtils.getPointOnLine(line.start, line.end, k);
let pos = [point[0], 0, point[2]];
if (k == 0) {
// first
position = MathUtils.addVector(pos, modelDistanceHalf);
scaleX = 1;
}
else if (k == count - 1) {
// last
position = MathUtils.addVector(position, MathUtils.scaleVector(forward.concat(), modelSize[0] / 2 + modelSize[0] * decimal / 2));
scaleX = decimal;
}
else {
// middle
position = MathUtils.addVector(position, modelDistance);
scaleX = 1;
}
const mat4 = MathUtils.createMat4(
[position[0], 0, position[2]],
angles,
[scaleX, scaleY, scaleZ]
);
result.push(mat4);
}
}
}
// 5. Generate Hole transform data
const fillHole = Utils.parseBoolean(wallData.fillHole, false);
if (fillHole && holes) {
for (let i = 0; i < holes.length; i++) {
const hole = holes[i];
const size = hole.size;
const loc = hole.loc;
const yPercent = loc[1];
let pos = MathUtils.getPointOnLine(startPosition, endPosition, loc[0]);
const scaleX = size[0] / modelSize[0];
// fill bottom
if (yPercent >= 0.001) {
// calc scale
let _scaleY = height * yPercent / modelSize[1];
const mat4 = MathUtils.createMat4(
[pos[0], 0, pos[2]],
angles,
[scaleX, _scaleY, scaleZ]
);
result.push(mat4);
}
// fill top
if (yPercent < 1) {
const topHeight = (1 - yPercent) * height - size[1];
if (topHeight > 0) {
let _scaleY = topHeight / modelSize[1];
const mat4 = MathUtils.createMat4(
[pos[0], height - topHeight, pos[2]],
angles,
[scaleX, _scaleY, scaleZ]
);
result.push(mat4);
}
}
}
}
return result;
}
_private._generateLines = (startPosition, endPosition, holes) => {
let lines = [];
const rootLineLength = MathUtils.getDistance(startPosition, endPosition);
// if the data contains holes
if (holes && holes.length > 0) {
// Sort holes
for (let j = 0; j < holes.length - 1; j++) {
for (let k = 0; k < holes.length - 1 - j; k++) {
if (holes[k].loc[0] > holes[k + 1].loc[0]) {
const temp = holes[k];
holes[k] = holes[k + 1];
holes[k + 1] = temp;
}
}
}
// Split root line
let preHolePercent = null;
holes.forEach(hole => {
const loc = hole['loc'];
const size = hole['size'];
const sizexPercent = size[0] / rootLineLength;
const subLineEndPercent = loc[0] - sizexPercent / 2;
// the ratio of the hole to the wall
let subLineStart, subLineEnd;
if (!preHolePercent) {
subLineStart = MathUtils.getPointOnLine(startPosition, endPosition, 0);
subLineEnd = MathUtils.getPointOnLine(startPosition, endPosition, subLineEndPercent);
}
else {
subLineStart = MathUtils.getPointOnLine(startPosition, endPosition, preHolePercent);
subLineEnd = MathUtils.getPointOnLine(startPosition, endPosition, subLineEndPercent);
}
lines.push({ start: subLineStart, end: subLineEnd });
preHolePercent = subLineEndPercent + sizexPercent;
});
// add last line
if (preHolePercent < 1) {
lines.push({
start: MathUtils.getPointOnLine(startPosition, endPosition, preHolePercent),
end: MathUtils.getPointOnLine(startPosition, endPosition, 1)
});
}
}
else {
// direct use root line
lines.push({ start: startPosition, end: endPosition });
}
return lines;
}
_private._mergeCreate = (url, matrices, pickIds, wallObject) => {
let options = { matrices, lights: true, pickIds };
return new Promise(function (resolve, reject) {
if (matrices && matrices.length) {
const resourceManager = wallObject.app.resourceManager;
resourceManager.loadModel(url, result => {
if (!wallObject.destroyed) {
result.node.setStyle(wallObject.bodyNode.getStyle());
wallObject.bodyNode.add(result.node);
}
resolve();
}, undefined, e => {
reject(e);
console.error(e);
}, options);
}
});
}
_private._getModelSize = async (url, wallObject) => {
return new Promise((resolve, reject) => {
wallObject.app.resourceManager.loadModel(url, function (e) {
const box = e.node.getOBB({ center: defCenter, halfSize: defHalfSize, rotation: defRotation });
const halfSize = box.halfSize;
const modelSize = [halfSize[0] * 2, halfSize[1] * 2, halfSize[2] * 2];
e.node.dispose();
resolve(modelSize);
}, function () { }, reject);
})
}
//#endregion
}
/**
* 加载墙体
* @param {Object} data - 墙体数据 (数据格式参考: https://wiki.uino.com/book/6538d1e88ea0ba892398fdf2/6523a7adae83a4dfb2c2cba4.html)
* @returns {Promise}
* @public
*/
load(data) {
let _private = this[__.private];
_private.setData(data);
return _private.load(this.object);
}
/**
* 导入墙体数据
* @param {Object} data
*/
onImport(data, options) {
if (!data) {
return;
}
super.onImport(data, options);
let _private = this[__.private];
_private.setData(data, options);
}
/**
* 导出墙体数据
* @returns {Object}
*/
onExport(options) {
super.onExport(options);
let _private = this[__.private];
return _private.getData(options);
}
/**
* Handles the loading of resources for the wall component.
*/
onLoadResource() {
let _private = this[__.private];
return _private.load(this.object);
}
}
Utils.registerClass("WallComponent", WallComponent);
export { WallComponent };