You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

515 lines
19 KiB

(function (global) {
'use strict';
var WorkbenchAgent = global.PWAgent;
var U = global.PWAgentUtil;
var nodeId = U && U.nodeId;
var walk = U && U.walk;
if (!WorkbenchAgent) {
console.error('[调试工作台] agent-pick 需先加载 agent-core');
return;
}
function log() {
try {
var fn = console.log || console.info;
fn.apply(console, ['[检视]'].concat(Array.prototype.slice.call(arguments)));
} catch (e) { /* */ }
}
/** 检视模式:点选节点(Creator 场景拾取) */
WorkbenchAgent.prototype._setPickMode = function (mode) {
this._pickMode = mode === 'inspect' ? 'inspect' : 'game';
try {
document.documentElement.classList.toggle('pw-inspect', this._pickMode === 'inspect');
var root = document.getElementById('pw-root') || document.body;
if (root && root.classList) {
root.classList.toggle('pw-inspect', this._pickMode === 'inspect');
}
} catch (e) { /* */ }
this._bindCanvasPick(this._pickMode === 'inspect');
log('setMode', this._pickMode);
return this._pickMode;
};
/**
* 壳层 / 游戏内共用入口。
* 同一位置重复点击时,在重叠节点里从上到下循环切换。
* @param {{clientX:number, clientY:number, space?:string}} params
*/
WorkbenchAgent.prototype._pickAt = function (params) {
var clientX = params && Number(params.clientX);
var clientY = params && Number(params.clientY);
if (!isFinite(clientX) || !isFinite(clientY)) {
return { uuid: this._selectedUuid || '', ok: false, reason: 'bad-coord' };
}
var r = this._hitTestDetailed(clientX, clientY);
var uuid = r.uuid || '';
var cycleInfo = '';
// 同点循环:位置接近时在命中列表里从上到下轮换
if (r.hits && r.hits.length) {
uuid = this._cyclePick(clientX, clientY, r.hits);
if (this._pickCycleList && this._pickCycleList.length > 1) {
cycleInfo = ' [' + (this._pickCycleIndex + 1) + '/' + this._pickCycleList.length + ']';
}
}
log('pickAt', clientX, clientY, '→', uuid || '(none)', (r.method || '') + cycleInfo);
var res = this._selectNode(uuid);
res.method = r.method || '';
res.cycle = this._pickCycleList && this._pickCycleList.length
? { index: this._pickCycleIndex, total: this._pickCycleList.length, names: this._pickCycleList.map(function (h) { return h.name; }) }
: null;
res.debug = r.debug || null;
return res;
};
/**
* 同一坐标重复点击 → 在命中列表(上→下)中循环。
* 位置偏移超过阈值则重置,从最上层重新开始。
*/
WorkbenchAgent.prototype._cyclePick = function (clientX, clientY, hits) {
var EPS = 8;
if (!hits || !hits.length) return '';
this._pickCycleList = hits;
this._pickCycleX = clientX;
this._pickCycleY = clientY;
var sameSpot = this._pickCycleArmed &&
Math.abs(clientX - (this._pickCycleLastX || 0)) <= EPS &&
Math.abs(clientY - (this._pickCycleLastY || 0)) <= EPS;
this._pickCycleLastX = clientX;
this._pickCycleLastY = clientY;
this._pickCycleArmed = true;
if (!sameSpot) {
this._pickCycleIndex = 0;
return hits[0].uuid;
}
// 从当前选中节点的下一层开始
var idx = 0;
for (var i = 0; i < hits.length; i++) {
if (hits[i].uuid === this._selectedUuid) {
idx = (i + 1) % hits.length;
break;
}
}
// 若上一选中不在列表里,则从 0 起
this._pickCycleIndex = idx;
return hits[idx].uuid;
};
WorkbenchAgent.prototype._bindCanvasPick = function (on) {
var self = this;
if (!this._onPickDown) {
this._onPickDown = function (ev) {
if (self._pickMode !== 'inspect') return;
if (ev && ev.preventDefault) ev.preventDefault();
if (ev && ev.stopPropagation) ev.stopPropagation();
self._pickAt({ clientX: ev.clientX, clientY: ev.clientY, space: 'viewport' });
};
}
if (this._pickBoundEls && this._pickBoundEls.length) {
for (var i = 0; i < this._pickBoundEls.length; i++) {
this._pickBoundEls[i].removeEventListener('pointerdown', this._onPickDown, true);
this._pickBoundEls[i].removeEventListener('mousedown', this._onPickDown, true);
}
}
this._pickBoundEls = [];
if (!on || this._pickMode !== 'inspect') return;
var targets = this._pickEventTargets();
for (var j = 0; j < targets.length; j++) {
if (!targets[j]) continue;
targets[j].addEventListener('pointerdown', this._onPickDown, true);
targets[j].addEventListener('mousedown', this._onPickDown, true);
this._pickBoundEls.push(targets[j]);
}
window.addEventListener('pointerdown', this._onPickDown, true);
window.addEventListener('mousedown', this._onPickDown, true);
this._pickBoundEls.push(window);
log('bind pick', this._pickBoundEls.length);
};
WorkbenchAgent.prototype._pickEventTargets = function () {
var list = [];
var ids = ['GameCanvas', 'Cocos3dGameContainer', 'GameDiv', 'content', 'pw-center', 'pw-game-host'];
for (var i = 0; i < ids.length; i++) {
var el = document.getElementById(ids[i]);
if (el && list.indexOf(el) < 0) list.push(el);
}
var cv = global.cc && global.cc.game && global.cc.game.canvas;
if (cv && list.indexOf(cv) < 0) list.push(cv);
return list;
};
WorkbenchAgent.prototype._getCanvasEl = function () {
return document.getElementById('GameCanvas') ||
(global.cc && global.cc.game && global.cc.game.canvas) ||
null;
};
WorkbenchAgent.prototype._uiCamera = function () {
var cc = this.cc;
if (!cc) return null;
try {
var scene = cc.director && cc.director.getScene && cc.director.getScene();
if (scene && cc.UITransform) {
var canvasNode = null;
walk(scene, function (n) {
if (!canvasNode && n && n.name === 'Canvas') canvasNode = n;
});
if (canvasNode && canvasNode.getComponent && cc.Camera) {
var cam = canvasNode.getComponent(cc.Camera);
if (cam) return cam;
}
}
if (cc.director && cc.director.root && cc.director.root.batcher2D && cc.director.root.batcher2D.camera) {
return cc.director.root.batcher2D.camera;
}
if (cc.director && cc.director.root && cc.director.root.cameraList && cc.director.root.cameraList.length) {
var list = cc.director.root.cameraList;
for (var i = 0; i < list.length; i++) {
if (list[i] && (list[i].camera || list[i])._uiCamera !== false) {
return list[i].camera || list[i];
}
}
}
} catch (e) { /* */ }
return null;
};
WorkbenchAgent.prototype._canvasWorldBox = function () {
var cc = this.cc;
if (!cc || !cc.UITransform) return null;
var scene = cc.director && cc.director.getScene && cc.director.getScene();
if (!scene) return null;
var canvasNode = null;
walk(scene, function (n) {
if (!canvasNode && n && n.name === 'Canvas' && n.activeInHierarchy) canvasNode = n;
});
if (!canvasNode || !canvasNode.getComponent) return null;
var uit = canvasNode.getComponent(cc.UITransform);
if (!uit || !uit.getBoundingBoxToWorld) return null;
var box = null;
try { box = uit.getBoundingBoxToWorld(); } catch (e) { return null; }
if (!box || !(box.width > 0) || !(box.height > 0)) return null;
return { x: box.x, y: box.y, width: box.width, height: box.height };
};
WorkbenchAgent.prototype._nodeDepth = function (node) {
var d = 0;
var p = node && node.parent;
while (p) {
d += 1;
p = p.parent;
}
return d;
};
/** 把 client 点换算到 Cocos 屏幕坐标(原点左下、CSS 像素,不乘 DPR) */
WorkbenchAgent.prototype._toCocosScreen = function (clientX, clientY) {
var canvas = this._getCanvasEl();
if (!canvas) return null;
var rect = canvas.getBoundingClientRect();
if (!rect.width || !rect.height) return null;
return {
sx: clientX - rect.left,
sy: rect.height - (clientY - rect.top),
rect: rect,
};
};
/**
* 多策略命中,返回最优节点。
* @returns {{uuid:string, method:string, debug:object}}
*/
WorkbenchAgent.prototype._hitTestDetailed = function (clientX, clientY) {
var cc = this.cc;
var empty = { uuid: '', method: 'none', debug: null };
if (!cc || !cc.director) {
empty.method = 'no-cc';
return empty;
}
var scene = cc.director.getScene();
if (!scene) {
empty.method = 'no-scene';
return empty;
}
var stack = [];
walk(scene, function (n) {
if (n && n.activeInHierarchy) stack.push(n);
});
var cam = this._uiCamera();
var screen = this._toCocosScreen(clientX, clientY);
var cbox = this._canvasWorldBox();
var worlds = [];
if (screen && cbox) {
var u = (clientX - screen.rect.left) / screen.rect.width;
var v = (clientY - screen.rect.top) / screen.rect.height;
worlds.push({
x: cbox.x + u * cbox.width,
y: cbox.y + (1 - v) * cbox.height,
tag: 'canvasBox',
});
}
if (screen && cc.view && cc.view.getDesignResolutionSize) {
var d = cc.view.getDesignResolutionSize();
if (d && d.width) {
var u2 = (clientX - screen.rect.left) / screen.rect.width;
var v2 = (clientY - screen.rect.top) / screen.rect.height;
worlds.push({ x: u2 * d.width, y: (1 - v2) * d.height, tag: 'design' });
worlds.push({ x: (u2 - 0.5) * d.width, y: (0.5 - v2) * d.height, tag: 'center' });
}
}
var self = this;
var uiCount = 0;
var samples = [];
var hits = [];
var hitUuids = {};
var getBox = function (uit) {
var box = null;
try {
if (uit && uit.getBoundingBoxToWorld) box = uit.getBoundingBoxToWorld();
} catch (e) { box = null; }
if (!box) return null;
var x = Number(box.x);
var y = Number(box.y);
var w = Number(box.width);
var h = Number(box.height);
if (!isFinite(x) || !isFinite(y) || !isFinite(w) || !isFinite(h)) return null;
if (w < 0) { x += w; w = -w; }
if (h < 0) { y += h; h = -h; }
return { x: x, y: y, width: w, height: h };
};
var siblingIndex = function (node) {
var p = node && node.parent;
if (!p || !p.children) return 0;
var idx = p.children.indexOf ? p.children.indexOf(node) : 0;
return idx < 0 ? 0 : idx;
};
var addHit = function (node, method, area) {
var id = nodeId(node);
if (hitUuids[id]) return;
hitUuids[id] = true;
hits.push({
uuid: id,
name: node.name || '',
isCanvas: (node.name || '') === 'Canvas',
depth: self._nodeDepth(node),
sIndex: siblingIndex(node),
area: area || 0,
method: method,
});
};
for (var s = 0; s < stack.length; s++) {
var node = stack[s];
if (!node || !node.getComponent) continue;
var uit = null;
try {
uit = (cc.UITransform && node.getComponent(cc.UITransform)) ||
node.getComponent('cc.UITransform') ||
node.getComponent('UITransform') ||
null;
} catch (e1) { uit = null; }
if (!uit) {
// 兜底:任何带 getBoundingBoxToWorld 的组件
var comps = node.components || [];
for (var ci = 0; ci < comps.length; ci++) {
if (comps[ci] && typeof comps[ci].getBoundingBoxToWorld === 'function') {
uit = comps[ci];
break;
}
}
}
if (!uit) continue;
uiCount += 1;
var box = getBox(uit);
if (!box) continue;
var area = box.width * box.height;
if (samples.length < 5) {
samples.push({
name: node.name || '?',
box: box,
});
}
// A: hitTest(camera, screen)
if (cam && screen && typeof uit.hitTest === 'function') {
try {
var sp = cc.Vec2 ? new cc.Vec2(screen.sx, screen.sy) : { x: screen.sx, y: screen.sy };
if (uit.hitTest(cam, sp)) addHit(node, 'hitTest', area);
} catch (e2) { /* */ }
}
// B: 世界 AABB(含 Rect.contains)
for (var w = 0; w < worlds.length; w++) {
var W = worlds[w];
var inside = false;
try {
var raw = uit.getBoundingBoxToWorld && uit.getBoundingBoxToWorld();
if (raw && typeof raw.contains === 'function' && cc.Vec2) {
if (raw.contains(new cc.Vec2(W.x, W.y))) inside = true;
}
} catch (e3) { /* */ }
if (!inside) {
inside = (W.x >= box.x && W.x <= box.x + box.width && W.y >= box.y && W.y <= box.y + box.height);
}
if (inside) {
addHit(node, 'aabb:' + W.tag, area);
break;
}
}
}
if (!hits.length) {
empty.method = uiCount ? 'miss' : 'no-uitransform';
empty.debug = {
worlds: worlds,
screen: screen ? { sx: screen.sx, sy: screen.sy, w: screen.rect.width, h: screen.rect.height, left: screen.rect.left, top: screen.rect.top } : null,
cbox: cbox,
nodes: stack.length,
uiCount: uiCount,
samples: samples,
cam: !!cam,
};
log('miss', JSON.stringify(empty.debug));
return empty;
}
// 视觉层级:上→下(更深、更后绘制的兄弟优先)
hits.sort(function (a, b) {
if (a.depth !== b.depth) return b.depth - a.depth;
if (a.sIndex !== b.sIndex) return b.sIndex - a.sIndex;
return a.area - b.area;
});
return {
uuid: hits[0].uuid,
method: hits[0].method,
hits: hits,
debug: {
best: hits[0].name,
hits: hits.length,
worlds: worlds,
cbox: cbox,
},
};
};
/** 兼容旧调用 */
WorkbenchAgent.prototype._hitTest = function (clientX, clientY) {
return this._hitTestDetailed(clientX, clientY).uuid || '';
};
WorkbenchAgent.prototype._ensureOverlay = function () {
var el = document.getElementById('pw-pick-overlay');
if (!el) {
el = document.createElement('div');
el.id = 'pw-pick-overlay';
el.className = 'pw-pick-overlay';
el.hidden = true;
(document.body || document.documentElement).appendChild(el);
}
this._overlay = el;
};
WorkbenchAgent.prototype._updateOverlay = function () {
this._ensureOverlay();
var node = this._findNode(this._selectedUuid);
var overlay = this._overlay;
if (!overlay) return;
if (!node) {
overlay.hidden = true;
return;
}
var box = this._nodeClientRect(node);
if (!box) {
overlay.hidden = true;
return;
}
overlay.hidden = false;
overlay.style.position = 'fixed';
overlay.style.left = box.left + 'px';
overlay.style.top = box.top + 'px';
overlay.style.width = box.width + 'px';
overlay.style.height = box.height + 'px';
overlay.style.zIndex = '99999';
};
WorkbenchAgent.prototype._nodeClientRect = function (node) {
var cc = this.cc;
var canvas = this._getCanvasEl();
if (!cc || !canvas || !node || !node.isValid) return null;
var uit = node.getComponent && node.getComponent(cc.UITransform);
if (!uit || !uit.getBoundingBoxToWorld) return null;
var box = null;
try { box = uit.getBoundingBoxToWorld(); } catch (e) { return null; }
if (!box) return null;
var rect = canvas.getBoundingClientRect();
var cbox = this._canvasWorldBox();
var vw, vh, ox, oy;
if (cbox) {
vw = Math.max(1, cbox.width);
vh = Math.max(1, cbox.height);
ox = cbox.x;
oy = cbox.y;
} else {
var design = cc.view && cc.view.getDesignResolutionSize ? cc.view.getDesignResolutionSize() : null;
vw = Math.max(1, (design && design.width) || rect.width);
vh = Math.max(1, (design && design.height) || rect.height);
ox = 0;
oy = 0;
}
var corners = [
{ x: box.x, y: box.y },
{ x: box.x + box.width, y: box.y },
{ x: box.x + box.width, y: box.y + box.height },
{ x: box.x, y: box.y + box.height },
];
var xs = [];
var ys = [];
for (var i = 0; i < 4; i++) {
var u = (corners[i].x - ox) / vw;
var v = 1 - (corners[i].y - oy) / vh;
xs.push(rect.left + u * rect.width);
ys.push(rect.top + v * rect.height);
}
var left = Math.min.apply(null, xs);
var right = Math.max.apply(null, xs);
var top = Math.min.apply(null, ys);
var bottom = Math.max.apply(null, ys);
return {
left: left,
top: top,
right: right,
bottom: bottom,
width: Math.max(2, right - left),
height: Math.max(2, bottom - top),
};
};
WorkbenchAgent.prototype._getOverlayRect = function () {
var overlay = this._overlay;
if (!overlay || overlay.hidden) return null;
return {
left: parseFloat(overlay.style.left) || 0,
top: parseFloat(overlay.style.top) || 0,
width: parseFloat(overlay.style.width) || 0,
height: parseFloat(overlay.style.height) || 0,
};
};
})(window);