import { BaseComponent, Camera, MathUtils, Math, ProjectionType, ViewMode } from '@uino/thing';
/**
* 小地图组件
* @class MinimapComponent
* @summary 小地图组件,在三维场景的角落叠加一个俯视摄像机视图作为小地图,并支持主摄像机方向标记。
* 通过附加的 Orthographic 俯视摄像机渲染场景缩略视图,同时在 DOM 上叠加方向指示图标,
* 帮助用户快速了解主摄像机在场景中的位置与朝向。支持位置/尺寸/布局自定义,
* 并内置关闭按钮、视口指示器和摄像机标记等图标。
* @public
* @memberof THING
* @example
* // 在场景中添加小地图组件
app.root.addComponent(THING.MinimapComponent, 'minimap', {
size: [250, 250],
layout: ['Right', 'Bottom'],
});
app.root.minimap.enable=true;
*/
class MinimapComponent extends BaseComponent {
/**
* @constructor
* @public
* @param {object} param 初始参数
* @param {Number[]} size 小地图尺寸
* @param {String[]} layout 小地图布局位置
*
*/
constructor(param) {
super(param);
let def_param = JSON.parse(JSON.stringify(this.def_param)); // 拷贝默认参数
this.options = { ...def_param, ...param }; // 与默认参数合并
}
onAdd(object) {
super.onAdd(object);
this._create();
this.enable = true;
}
onRemove() {
this._destroy()
super.onRemove();
}
onUpdate() {
this._update();
}
get size() {
return this.options.size
}
/**
* 设置/获取小地图的尺寸
* @public
* @type {Array}
* @example
* minimap.size = [300, 300]
*/
set size(val) {
this._destroy()
this.options.size = val
this._create()
}
get width() {
return this.options.size[0]
}
set width(val) {
this._destroy()
this.options.size[0] = val
this._create()
}
get height() {
return this.options.size[1]
}
set height(val) {
this._destroy()
this.options.size[1] = val
this._create()
}
get distance() {
return this.options.distance
}
set distance(val) {
this.options.distance = val
if (this.minimapCamera) {
let position = this.minimapCamera.position;
this.minimapCamera.position = [position[0], val, position[2]]
}
}
get layout() {
return this.options.layout
}
/**
* 设置/获取小地图的布局
* @public
* @type {String[]}
* @example
* minimap.layout = ["Left", "Bottom"]
*/
set layout(val) {
this._destroy()
this.options.layout = val
this._create()
}
get position() {
return this.options.position
}
/**
* 设置/获取小地图的位置 如果设置了 position 就忽略 layout
* @public
* @type {Number}
* @example
* minimap.position = [1,1]
*/
set position(val) {
this._destroy()
this.options.position = val
this._create()
}
/**
* 设置/获取小地图是否启用
* @public
* @type {Array}
* @example
* minimap.enable = true
*/
get enable() {
return this._enable;
}
set enable(value) {
this._enable = value;
this['minimap-border'].style.display = value ? 'block' : 'none';
this.minimapCamera.visible = value;
}
_create() {
this._createMinimapDom();
this._createMinimapView();
}
_destroy() {
if (this['minimap-border'] != null) {
this.app.container.removeChild(this['minimap-border']);
this['minimap-border'] = null;
}
this.minimapCamera.destroy();
}
// 创建小地图的dom
_createMinimapDom() {
let _style_ =
`.minimap-border {
position : absolute;
border: 5px solid rgba(255, 255, 255, 0.8);
border-radius: 3px;
display: block;
box-sizing: border-box;
overflow:hidden;
}
.minimap-viewport {
position : absolute;
}
.minimap-target {
position : absolute;
}
.minimap-close {
position : absolute;
right : 1px;
top : 1px;
}
`;
let style = document.createElement("style")
style.type = "text/css";
style.innerHTML = _style_;
document.body.appendChild(style);
let _classNames = [];
function _getClassNames(options) {
for (let key in options) {
if (key === 'className') {
_classNames.push({ key: options[key], show: options['show'], size: options['size'], src: options['src'] })
}
if (typeof options[key] === 'object') {
_getClassNames(options[key])
}
}
}
_getClassNames(this.options);
// 创建 div
_classNames.forEach((cn) => {
let div;
if (cn.key === 'minimap-border') {
div = document.createElement("div");
let o = this._getDomOrigin();
div.style.left = o.left === null ? null : o.left + 'px';
div.style.right = o.right === null ? null : o.right + 'px';
div.style.top = o.top === null ? null : o.top + 'px';
div.style.bottom = o.bottom === null ? null : o.bottom + 'px';
this.app.container.appendChild(div);
this.miniDomDiv = div;
}
else {
div = document.createElement("img");
div.src = cn.src; // 设置背景图片
this.miniDomDiv.appendChild(div)
}
div.className = cn.key;
div.style.display = cn.show ? 'block' : 'none'; // 是否显示某个icon
div.style.width = cn.size[0] + 'px'; // 设置宽度
div.style.height = cn.size[1] + 'px'; // 设置高度
this[cn.key] = div; // 赋给全局
// 如果是关闭按钮,则绑定关闭事件
let that = this;
if (cn.key === 'minimap-close') {
if (cn.show) {
div.style.right = '0px';
div.style.top = '0px';
}
div.onclick = function () {
if (that.enable) { that.enable = false; }
}
}
})
}
// 创建小地图摄影机
_createMinimapView() {
var minimapCamera = new Camera();
minimapCamera.enableViewport = true;
minimapCamera.viewport = [this._getDomOrigin().left, this._getDomOrigin().top, this.options.size[0], this.options.size[1]];
minimapCamera.projectionType = ProjectionType.Orthographic;
minimapCamera.viewMode = ViewMode.Top;
minimapCamera.postEffect.enable = false;
minimapCamera.enable = false;
minimapCamera.background = null;
minimapCamera.control.enableAdjustTargetPosition = false;
let position = minimapCamera.position
if (this.options.distance) {
minimapCamera.position = [position[0], this.options.distance, position[2]]
}
else {
this.options.distance = position[2]
}
this.minimapCamera = minimapCamera;
}
// 刷新一次。主要刷新状态,例如图标的显隐
_refresh() {
}
// 每帧刷新。更新小地图中图标的方向和位置
_update() {
// 如果是激活状态
if (!this.enable) return;
let iconConfig = this.options.iconConfig;
let target = iconConfig.target;
let viewport = iconConfig.viewport;
let ori = this._getDomOrigin();
let pos = this._getPos_world2screen();
let ang = this._getAng_world2screen();
if (this._getShow(target)) { // 如果显示主摄像机标记
this['minimap-target'].style.left = pos.left - target.size[0] / 2 + 'px';
this['minimap-target'].style.top = pos.top - target.size[1] / 2 + 'px';
}
if (this._getShow(viewport)) { // 如果显示视口图片
this['minimap-viewport'].style.left = pos.left - viewport.size[0] / 2 + 'px';
this['minimap-viewport'].style.top = pos.top - viewport.size[1] + 'px';
this['minimap-viewport'].style.transform = 'rotate(' + (ang) + 'deg)';
this['minimap-viewport'].style.transformOrigin = "50% 100%";
}
}
// 根据小地图的分布获取dom的原点
_getDomOrigin() {
let left = 0
let top = 0
let width = this.app.container.clientWidth - this.options.size[0]
let height = this.app.container.clientHeight - this.options.size[1]
if (this.options.position) {
left = this.options.position[0] * width;
top = this.options.position[1] * height
}
else if (this.options.layout) {
let layout = this.options.layout;
if (layout[0].toLowerCase() === 'left') {
left = 0
}
else if (layout[0].toLowerCase() === 'right') {
left = width
}
else if (layout[0].toLowerCase() === 'center') {
left = 0.5 * width
}
if (top = layout[1].toLowerCase() === 'top') {
top = 0
}
else if (top = layout[1].toLowerCase() === 'bottom') {
top = height
}
else if (top = layout[1].toLowerCase() === 'center') {
top = 0.5 * height
}
}
return {
left, top
}
}
// 转换成小地图坐标
_getPos_world2screen() {
// 把小地图的摄影机坐标系按比例转成小地图视口的屏幕坐标
let _cameraPos_ = this.minimapCamera.position;
let _width_ = this.minimapCamera.orthoRect[1] - this.minimapCamera.orthoRect[0];
let _height_ = this.minimapCamera.orthoRect[3] - this.minimapCamera.orthoRect[2];
let _oriX_ = _cameraPos_[0] - _width_ / 2;
let _oriZ_ = _cameraPos_[2] - _height_ / 2;
// 计算比例
let _mainCameraPos_ = this.app.camera.position;
let _scale_x_ = (_mainCameraPos_[0] - _oriX_) / _width_;
let _scale_z_ = (_mainCameraPos_[2] - _oriZ_) / _height_;
// 2d坐标
let left = _scale_x_ * this.options.size[0];
let top = _scale_z_ * this.options.size[1];
return { left, top };
}
// 转换角度
_getAng_world2screen() {
const camDir = this.app.camera.forward;
const angleRad = MathUtils.vec3.angle([0, 0, -1], [camDir[0], 0, camDir[2]]);
let v3 = [];
MathUtils.vec3.cross(v3, [0, 0, -1], [camDir[0], 0, camDir[2]]);
const angle = MathUtils.radToDeg(angleRad);
return 360 - (v3[1] > 0 ? angle : 360 - angle);
}
// 获取是否显示
_getShow(icon) {
return icon.show;
}
// 获取默认参数
get def_param() {
return {
/* --- 小地图配置项 --- */
className: "minimap-border",
show: true,
borderWidth: 5, // 边框宽度
layout: ["Left", "Top"], // 小地图的布局
size: [200, 200], // 小地图窗口的大小
opacity: 1, // 小地图窗口的透明度
scaleFactor: 1, // 视口缩放
followTarget: false, // 主摄影机跟随视口鼠标点击位置
/* --- 图标配置项 --- */
iconConfig: {
// 关闭按钮
close: {
className: "minimap-close",
show: true,
src: "http://www.thingjs.com/static/images/minimap2.png",
size: [20, 20]
},
// 视口
viewport: {
className: "minimap-viewport",
show: true,
src: "http://www.thingjs.com/static/images/minimap1.png",
size: [40, 30]
},
// 主摄影机标记
target: {
className: "minimap-target",
show: true,
src: "http://www.thingjs.com/static/images/minimap0.png",
size: [15, 15]
}
}
}
}
}
export { MinimapComponent }