pinball/assets/_Game/Scripts/Gizmos/Gizmos2D.ts

429 lines
14 KiB
TypeScript

import { _decorator, CCObject, Color, Component, Graphics, IVec2Like, Layers, Node, toRadian, Vec3 } from 'cc';
import { Editor } from './Define';
const { ccclass, executeInEditMode } = _decorator;
class GizmosRenderer {
private _graphic: Graphics;
private _drawCalls: ((graphic: Graphics) => void)[] = [];
public addDrawCall(drawCall: (graphic: Graphics) => void) {
this._drawCalls.push(drawCall);
}
constructor(graphic: Graphics) {
this._graphic = graphic;
}
public clear() {
this._graphic.clear();
}
public setLayer(layer: Layers.Enum) {
this._graphic.node.layer = layer;
}
public draw() {
this._drawCalls.forEach((drawCall) => {
drawCall(this._graphic);
});
this._drawCalls = [];
}
}
@ccclass('Gizmos2D.GizmosDebugDraw')
@executeInEditMode
class GizmosDebugDraw extends Component {
private _renderers: Map<string, GizmosRenderer> = new Map();
private _color: Color = Gizmos2D.DEFAULT_COLOR;
private _useLocalPosition: boolean = false;
private _layer: Layers.Enum = Gizmos2D.DEFAULT_LAYER;
private _components: (new () => Component)[] = [];
protected update(dt: number): void {
//only call in editor
this.callNodeDrawGizmos();
this._renderers.forEach((renderer) => {
renderer.clear();
renderer.draw();
});
this._color = Gizmos2D.DEFAULT_COLOR;
this._useLocalPosition = false;
this._layer = Gizmos2D.DEFAULT_LAYER;
}
private callNodeDrawGizmos() {
if (Editor) {
const selectedList: string[] = Editor.Selection.getSelected('node');
for (let i = 0; i < this._components.length; i++) {
const comp: Component = this.node.parent.getComponent(this._components[i]);
comp.onDrawGizmos?.();
if (selectedList.includes(this.node.parent.uuid)) {
comp.onDrawGizmosSelected?.();
}
}
}
}
private createRenderer(color: Color) {
const hex = color.toHEX();
const g = new Node(`color ${hex}`).addComponent(Graphics);
g.lineWidth = 5;
g.strokeColor = color;
g.fillColor = color;
g.node.layer = this.node.layer;
g.node.parent = this.node;
const renderer = new GizmosRenderer(g);
return renderer;
}
private getRenderer(color: Color): GizmosRenderer {
const hex = color.toHEX();
let renderer = this._renderers.get(hex);
if (!renderer) {
renderer = this.createRenderer(color);
this._renderers.set(hex, renderer);
}
return renderer;
}
private worldToLocal(world: IVec2Like): IVec2Like {
const local = new Vec3();
this.node.inverseTransformPoint(local, new Vec3(world.x, world.y));
return local;
}
public registerDrawGizmos(component: new () => Component) {
this._components.push(component);
}
public setColor(color: Color) {
this._color = color;
}
public setUseLocalPosition(value: boolean) {
this._useLocalPosition = value;
}
public setLayer(layer: Layers.Enum) {
this._layer = layer;
}
public clearAll() {
this._renderers.forEach((renderer) => renderer.clear());
}
public drawLine(point1: IVec2Like, point2: IVec2Like) {
const color = this._color.clone();
const renderer = this.getRenderer(color);
renderer.setLayer(this._layer);
const p1 = this._useLocalPosition ? point1 : this.worldToLocal(point1);
const p2 = this._useLocalPosition ? point1 : this.worldToLocal(point2);
renderer.addDrawCall((g) => {
g.moveTo(p1.x, p1.y);
g.lineTo(p2.x, p2.y);
g.stroke();
});
}
public drawLineList(points: IVec2Like[], close: boolean = false) {
const color = this._color.clone();
const renderer = this.getRenderer(color);
renderer.setLayer(this._layer);
const pointList = this._useLocalPosition ? points : points.map((p) => this.worldToLocal(p));
renderer.addDrawCall((g) => {
if (points.length > 0) {
const p0 = pointList[0];
g.moveTo(p0.x, p0.y);
for (let i = 1; i < pointList.length; i++) {
const p = pointList[i];
g.lineTo(p.x, p.y);
}
if (close) g.close();
g.stroke();
}
});
}
public drawCircle(center: IVec2Like, radius: number) {
const color = this._color.clone();
const renderer = this.getRenderer(color);
renderer.setLayer(this._layer);
const c = this._useLocalPosition ? center : this.worldToLocal(center);
renderer.addDrawCall((g) => {
g.circle(c.x, c.y, radius);
g.stroke();
});
}
public drawSolidCircle(center: IVec2Like, radius: number) {
const color = this._color.clone();
const renderer = this.getRenderer(color);
renderer.setLayer(this._layer);
const c = this._useLocalPosition ? center : this.worldToLocal(center);
renderer.addDrawCall((g) => {
g.circle(c.x, c.y, radius);
g.fill();
});
}
public drawRect(position: IVec2Like, width: number, height: number) {
const color = this._color.clone();
const renderer = this.getRenderer(color);
renderer.setLayer(this._layer);
const p = this._useLocalPosition ? position : this.worldToLocal(position);
renderer.addDrawCall((g) => {
g.rect(p.x, p.y, width, height);
g.stroke();
});
}
public drawSolidRect(position: IVec2Like, width: number, height: number) {
const color = this._color.clone();
const renderer = this.getRenderer(color);
renderer.setLayer(this._layer);
const p = this._useLocalPosition ? position : this.worldToLocal(position);
const topLeft = {
x: p.x - width / 2,
y: p.y - height / 2,
};
renderer.addDrawCall((g) => {
g.rect(topLeft.x, topLeft.y, width, height);
g.fill();
});
}
public drawSolidPolygon(points: IVec2Like[]) {
const color = this._color.clone();
const renderer = this.getRenderer(color);
renderer.setLayer(this._layer);
const pointList = this._useLocalPosition ? points : points.map((p) => this.worldToLocal(p));
renderer.addDrawCall((g) => {
if (points.length > 0) {
const p0 = pointList[0];
g.moveTo(p0.x, p0.y);
for (let i = 1; i < pointList.length; i++) {
const p = pointList[i];
g.lineTo(p.x, p.y);
}
g.close();
g.fill();
}
});
}
public drawEllipse(center: IVec2Like, radiusX: number, radiusY: number) {
const color = this._color.clone();
const renderer = this.getRenderer(color);
renderer.setLayer(this._layer);
const c = this._useLocalPosition ? center : this.worldToLocal(center);
renderer.addDrawCall((g) => {
g.ellipse(c.x, c.y, radiusX, radiusY);
g.stroke();
});
}
public drawSolidEllipse(center: IVec2Like, radiusX: number, radiusY: number) {
const color = this._color.clone();
const renderer = this.getRenderer(color);
renderer.setLayer(this._layer);
const c = this._useLocalPosition ? center : this.worldToLocal(center);
renderer.addDrawCall((g) => {
g.ellipse(c.x, c.y, radiusX, radiusY);
g.fill();
});
}
public drawArc(
center: IVec2Like,
radius: number,
startAngle: number,
endAngle: number,
counterclockwise: boolean = false,
) {
const color = this._color.clone();
const renderer = this.getRenderer(color);
renderer.setLayer(this._layer);
const c = this._useLocalPosition ? center : this.worldToLocal(center);
renderer.addDrawCall((g) => {
g.arc(c.x, c.y, radius, toRadian(startAngle), toRadian(endAngle), counterclockwise);
g.stroke();
});
}
public drawSolidArc(
center: IVec2Like,
radius: number,
startAngle: number,
endAngle: number,
counterclockwise: boolean = false,
) {
const color = this._color.clone();
const renderer = this.getRenderer(color);
renderer.setLayer(this._layer);
const c = this._useLocalPosition ? center : this.worldToLocal(center);
renderer.addDrawCall((g) => {
g.arc(c.x, c.y, radius, toRadian(startAngle), toRadian(endAngle), counterclockwise);
g.lineTo(c.x, c.y);
g.fill();
});
}
public drawBezierCurves(point1: IVec2Like, point2: IVec2Like, point3: IVec2Like, point4: IVec2Like) {
const color = this._color.clone();
const renderer = this.getRenderer(color);
renderer.setLayer(this._layer);
const p1 = this._useLocalPosition ? point1 : this.worldToLocal(point1);
const p2 = this._useLocalPosition ? point2 : this.worldToLocal(point2);
const p3 = this._useLocalPosition ? point3 : this.worldToLocal(point3);
const p4 = this._useLocalPosition ? point4 : this.worldToLocal(point4);
renderer.addDrawCall((g) => {
g.moveTo(p1.x, p1.y);
g.bezierCurveTo(p2.x, p2.y, p3.x, p3.y, p4.x, p4.y);
g.stroke();
});
}
public drawQuadraticCurve(point1: IVec2Like, point2: IVec2Like, point3: IVec2Like) {
const color = this._color.clone();
const renderer = this.getRenderer(color);
renderer.setLayer(this._layer);
const p1 = this._useLocalPosition ? point1 : this.worldToLocal(point1);
const p2 = this._useLocalPosition ? point2 : this.worldToLocal(point2);
const p3 = this._useLocalPosition ? point3 : this.worldToLocal(point3);
renderer.addDrawCall((g) => {
g.moveTo(p1.x, p1.y);
g.quadraticCurveTo(p2.x, p2.y, p3.x, p3.y);
g.stroke();
});
}
}
export default class Gizmos2D {
public static readonly DEFAULT_COLOR = Color.BLUE;
public static readonly DEFAULT_LAYER = Layers.Enum.GIZMOS;
private static getDebugNode(node: Node) {
let debugNode = node.getComponentInChildren(GizmosDebugDraw);
if (!debugNode) {
debugNode = new Node('DEBUG_DRAW_NODE').addComponent(GizmosDebugDraw);
debugNode.node.layer = this.DEFAULT_LAYER;
debugNode.node.hideFlags |= CCObject.Flags.DontSave | CCObject.Flags.HideInHierarchy;
debugNode.node.parent = node;
}
return debugNode;
}
public static registerDrawGizmos(node: Node, component: new () => Component) {
this.getDebugNode(node).registerDrawGizmos(component);
}
public static beginColor(node: Node, color: Color) {
this.getDebugNode(node).setColor(color);
}
public static beginLocalPosition(node: Node) {
this.getDebugNode(node).setUseLocalPosition(true);
}
public static endLocalPosition(node: Node) {
this.getDebugNode(node).setUseLocalPosition(false);
}
public static beginLayer(node: Node, layer: Layers.Enum) {
this.getDebugNode(node).setLayer(layer);
}
public static clearAll(node: Node) {
this.getDebugNode(node).clearAll();
}
public static drawLine(node: Node, point1: IVec2Like, point2: IVec2Like) {
const debugNode = this.getDebugNode(node);
debugNode.drawLine(point1, point2);
}
public static drawLineList(node: Node, points: IVec2Like[], close: boolean = false) {
const debugNode = this.getDebugNode(node);
debugNode.drawLineList(points, close);
}
public static drawCircle(node: Node, center: IVec2Like, radius: number) {
const debugNode = this.getDebugNode(node);
debugNode.drawCircle(center, radius);
}
public static drawSolidCircle(node: Node, center: IVec2Like, radius: number) {
const debugNode = this.getDebugNode(node);
debugNode.drawSolidCircle(center, radius);
}
public static drawRect(node: Node, position: IVec2Like, width: number, height: number) {
const debugNode = this.getDebugNode(node);
debugNode.drawRect(position, width, height);
}
public static drawSolidRect(node: Node, position: IVec2Like, width: number, height: number) {
const debugNode = this.getDebugNode(node);
debugNode.drawSolidRect(position, width, height);
}
public static drawSolidPolygon(node: Node, positions: IVec2Like[]) {
const debugNode = this.getDebugNode(node);
debugNode.drawSolidPolygon(positions);
}
public static drawEllipse(node: Node, center: IVec2Like, radiusX: number, radiusY: number) {
const debugNode = this.getDebugNode(node);
debugNode.drawEllipse(center, radiusX, radiusY);
}
public static drawSolidEllipse(node: Node, center: IVec2Like, radiusX: number, radiusY: number) {
const debugNode = this.getDebugNode(node);
debugNode.drawSolidEllipse(center, radiusX, radiusY);
}
public static drawArc(
node: Node,
center: IVec2Like,
radius: number,
startAngle: number,
endAngle: number,
counterclockwise: boolean = false,
) {
const debugNode = this.getDebugNode(node);
debugNode.drawArc(center, radius, startAngle, endAngle, counterclockwise);
}
public static drawSolidArc(
node: Node,
center: IVec2Like,
radius: number,
startAngle: number,
endAngle: number,
counterclockwise: boolean = false,
) {
const debugNode = this.getDebugNode(node);
debugNode.drawSolidArc(center, radius, startAngle, endAngle, counterclockwise);
}
public static drawBezierCurves(
node: Node,
point1: IVec2Like,
point2: IVec2Like,
point3: IVec2Like,
point4: IVec2Like,
) {
const debugNode = this.getDebugNode(node);
debugNode.drawBezierCurves(point1, point2, point3, point4);
}
public static drawQuadraticCurve(node: Node, point1: IVec2Like, point2: IVec2Like, point3: IVec2Like) {
const debugNode = this.getDebugNode(node);
debugNode.drawQuadraticCurve(point1, point2, point3);
}
}