diff --git a/tools/winscope-ng/src/viewers/common/rectangle.ts b/tools/winscope-ng/src/viewers/common/rectangle.ts index 7ecb8d53e..ee2ff9cfb 100644 --- a/tools/winscope-ng/src/viewers/common/rectangle.ts +++ b/tools/winscope-ng/src/viewers/common/rectangle.ts @@ -19,8 +19,6 @@ export interface Rectangle { bottomRight: Point; label: string; transform: RectTransform | null; - height: number; - width: number; isVisible: boolean; isDisplay: boolean; ref: any; @@ -28,6 +26,7 @@ export interface Rectangle { displayId: number; isVirtual: boolean; isClickable: boolean; + cornerRadius: number; } export interface Point { @@ -35,6 +34,11 @@ export interface Point { y: number } +export interface Size { + width: number, + height: number +} + export interface RectTransform { matrix?: RectMatrix; dsdx?: number; diff --git a/tools/winscope-ng/src/viewers/components/rects/canvas.ts b/tools/winscope-ng/src/viewers/components/rects/canvas.ts index caa111549..9884aaa2a 100644 --- a/tools/winscope-ng/src/viewers/components/rects/canvas.ts +++ b/tools/winscope-ng/src/viewers/components/rects/canvas.ts @@ -23,9 +23,12 @@ export class Canvas { private static readonly TARGET_SCENE_DIAGONAL = 4; private static readonly RECT_COLOR_HIGHLIGHTED = new THREE.Color(0xD2E3FC); private static readonly RECT_EDGE_COLOR = 0x000000; + private static readonly RECT_EDGE_COLOR_ROUNDED = 0x848884; private static readonly LABEL_CIRCLE_COLOR = 0x000000; private static readonly LABEL_LINE_COLOR = 0x000000; private static readonly LABEL_LINE_COLOR_HIGHLIGHTED = 0x808080; + private static readonly OPACITY_REGULAR = 0.75; + private static readonly OPACITY_OVERSIZED = 0.25; private canvasRects: HTMLCanvasElement; private canvasLabels: HTMLElement; @@ -121,16 +124,11 @@ export class Canvas { private drawRects(rects: Rect3D[]) { this.clickableObjects = []; rects.forEach(rect => { - const rectGeometry = new THREE.PlaneGeometry(rect.width, rect.height); - - const rectMesh = this.makeRectMesh(rect, rectGeometry); - const rectEdges = this.makeRectLineSegments(rectMesh, rectGeometry); + const rectMesh = this.makeRectMesh(rect); const transform = this.toMatrix4(rect.transform); rectMesh.applyMatrix4(transform); - rectEdges.applyMatrix4(transform); this.scene?.add(rectMesh); - this.scene?.add(rectEdges); if (rect.isClickable) { this.clickableObjects.push(rectMesh); @@ -203,11 +201,54 @@ export class Canvas { } private makeRectMesh( - rect: Rect3D, - geometry: THREE.PlaneGeometry, + rect: Rect3D ): THREE.Mesh { - let color: THREE.Color; + const rectShape = this.createRectShape(rect); + const rectGeometry = new THREE.ShapeGeometry(rectShape); + const rectBorders = this.createRectBorders(rect, rectGeometry); + let opacity = Canvas.OPACITY_REGULAR; + if (rect.isOversized) { + opacity = Canvas.OPACITY_OVERSIZED; + } + + // Crate mesh to draw + const mesh = new THREE.Mesh( + rectGeometry, + new THREE.MeshBasicMaterial({ + color: this.getColor(rect), + opacity: opacity, + transparent: true, + })); + + mesh.add(rectBorders); + mesh.position.x = 0; + mesh.position.y = 0; + mesh.position.z = rect.topLeft.z; + mesh.name = rect.id; + + return mesh; + } + + private createRectShape(rect: Rect3D): THREE.Shape { + const bottomLeft: Point3D = { x: rect.topLeft.x, y: rect.bottomRight.y, z: rect.topLeft.z }; + const topRight: Point3D = { x: rect.bottomRight.x, y: rect.topLeft.y, z: rect.bottomRight.z }; + + // Create (rounded) rect shape + return new THREE.Shape() + .moveTo(rect.topLeft.x, rect.topLeft.y + rect.cornerRadius) + .lineTo(bottomLeft.x, bottomLeft.y - rect.cornerRadius) + .quadraticCurveTo(bottomLeft.x, bottomLeft.y, bottomLeft.x + rect.cornerRadius, bottomLeft.y) + .lineTo(rect.bottomRight.x - rect.cornerRadius, rect.bottomRight.y) + .quadraticCurveTo(rect.bottomRight.x, rect.bottomRight.y, rect.bottomRight.x, rect.bottomRight.y - rect.cornerRadius) + .lineTo(topRight.x, topRight.y + rect.cornerRadius) + .quadraticCurveTo(topRight.x, topRight.y, topRight.x - rect.cornerRadius, topRight.y) + .lineTo(rect.topLeft.x + rect.cornerRadius, rect.topLeft.y) + .quadraticCurveTo(rect.topLeft.x, rect.topLeft.y, rect.topLeft.x, rect.topLeft.y + rect.cornerRadius); + } + + private getColor(rect: Rect3D): THREE.Color { + let color: THREE.Color = Canvas.RECT_COLOR_HIGHLIGHTED; switch (rect.colorType) { case ColorType.VISIBLE: { // green (darkness depends on z order) @@ -230,32 +271,29 @@ export class Canvas { break; } } - - const mesh = new THREE.Mesh( - geometry, - new THREE.MeshBasicMaterial({ - color: color, - opacity: 0.75, - transparent: true, - })); - mesh.position.x = rect.center.x; - mesh.position.y = rect.center.y; - mesh.position.z = rect.center.z; - mesh.name = rect.id; - - return mesh; + return color; } - private makeRectLineSegments(rectMesh: THREE.Mesh, geometry: THREE.PlaneGeometry): THREE.LineSegments { - const edgeGeo = new THREE.EdgesGeometry(geometry); - const edgeMaterial = new THREE.LineBasicMaterial({ - color: Canvas.RECT_EDGE_COLOR, linewidth: 1 - }); - const edgeSegments = new THREE.LineSegments( + private createRectBorders(rect: Rect3D, rectGeometry: THREE.ShapeGeometry): THREE.LineSegments { + // create line edges for rect + const edgeGeo = new THREE.EdgesGeometry(rectGeometry); + let edgeMaterial: THREE.Material; + if (rect.cornerRadius) { + edgeMaterial = new THREE.LineBasicMaterial({ + color: Canvas.RECT_EDGE_COLOR_ROUNDED, + linewidth: 1 + }); + } else { + edgeMaterial = new THREE.LineBasicMaterial({ + color: Canvas.RECT_EDGE_COLOR, + linewidth: 1 + }); + } + const lineSegments = new THREE.LineSegments( edgeGeo, edgeMaterial ); - edgeSegments.position.set(rectMesh.position.x, rectMesh.position.y, rectMesh.position.z); - return edgeSegments; + lineSegments.computeLineDistances(); + return lineSegments; } private makeLabelCircleMesh(circle: Circle3D): THREE.Mesh { diff --git a/tools/winscope-ng/src/viewers/components/rects/mapper3d.ts b/tools/winscope-ng/src/viewers/components/rects/mapper3d.ts index ab63760cc..1d366b0f9 100644 --- a/tools/winscope-ng/src/viewers/components/rects/mapper3d.ts +++ b/tools/winscope-ng/src/viewers/components/rects/mapper3d.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import {Rectangle} from "viewers/common/rectangle"; +import {Rectangle, Size} from "viewers/common/rectangle"; import { Box3D, ColorType, @@ -37,6 +37,14 @@ class Mapper3D { private static readonly ZOOM_FACTOR_MIN = 0.1; private static readonly ZOOM_FACTOR_MAX = 8.5; private static readonly ZOOM_FACTOR_STEP = 0.2; + private static readonly IDENTITY_TRANSFORM: Transform3D = { + dsdx: 1, + dsdy: 0, + tx: 0, + dtdx: 0, + dtdy: 1, + ty: 0 + }; private rects: Rectangle[] = []; private highlightedRectIds: string[] = []; @@ -169,102 +177,119 @@ class Mapper3D { let z = 0; - const displays = rects2d.filter((rect2d) => rect2d.isDisplay); - // Arbitrary max size for a rect (2x the maximum display) - let maxDimension = Number.MAX_VALUE; - - if (displays.length > 0) { - maxDimension = Math.max( - ...displays.map((rect2d): number => Math.max(rect2d.width, rect2d.height)) - ) * 2; - } + const maxDisplaySize = this.getMaxDisplaySize(rects2d); const rects3d = rects2d.map((rect2d): Rect3D => { - const identity: Transform3D = { - dsdx: 1, - dsdy: 0, - tx: 0, - dtdx: 0, - dtdy: 1, - ty: 0 - }; - - let center: Point3D = { - x: rect2d.topLeft.x + rect2d.width / 2, - y: rect2d.topLeft.y + rect2d.height / 2, - z: z - }; - z -= Mapper3D.Z_SPACING_MAX * this.zSpacingFactor; const darkFactor = rect2d.isVisible ? (visibleRectsTotal - visibleRectsSoFar++) / visibleRectsTotal : (nonVisibleRectsTotal - nonVisibleRectsSoFar++) / nonVisibleRectsTotal; - - let colorType: ColorType; - if (this.highlightedRectIds.includes(rect2d.id)) { - colorType = ColorType.HIGHLIGHTED; - } else if (rect2d.isVisible) { - colorType = ColorType.VISIBLE; - } else { - colorType = ColorType.NOT_VISIBLE; - } - - let transform: Transform3D; - if (rect2d.transform?.matrix) { - transform = { - dsdx: rect2d.transform.matrix.dsdx, - dsdy: rect2d.transform.matrix.dsdy, - tx: rect2d.transform.matrix.tx, - dtdx: rect2d.transform.matrix.dtdx, - dtdy: rect2d.transform.matrix.dtdy, - ty: rect2d.transform.matrix.ty, - }; - } else { - transform = identity; - } - - let height = rect2d.height; - let width = rect2d.width; - - // Crop oversized rectangles (e.g. BackColorSurface to make it easier to see elements) - if (width > maxDimension && height > maxDimension) { - width = maxDimension; - height = maxDimension; - // centralize the new rect - center = { - x: maxDimension / 4, - y: 0, - z: center.z - }; - } - - return { + const rect = { id: rect2d.id, - center: center, - width: width, - height: height, + topLeft: { + x: rect2d.topLeft.x, + y: rect2d.topLeft.y, + z: z + }, + bottomRight: { + x: rect2d.bottomRight.x, + y: rect2d.bottomRight.y, + z: z + }, + isOversized: false, + cornerRadius: rect2d.cornerRadius, darkFactor: darkFactor, - colorType: colorType, + colorType: this.getColorType(rect2d), isClickable: rect2d.isClickable, - transform: transform, + transform: this.getTransform(rect2d), }; + return this.cropOversizedRect(rect, maxDisplaySize); }); return rects3d; } + private getColorType(rect2d: Rectangle): ColorType { + let colorType: ColorType; + if (this.highlightedRectIds.includes(rect2d.id)) { + colorType = ColorType.HIGHLIGHTED; + } else if (rect2d.isVisible) { + colorType = ColorType.VISIBLE; + } else { + colorType = ColorType.NOT_VISIBLE; + } + return colorType; + } + + private getTransform(rect2d: Rectangle): Transform3D { + let transform: Transform3D; + if (rect2d.transform?.matrix) { + transform = { + dsdx: rect2d.transform.matrix.dsdx, + dsdy: rect2d.transform.matrix.dsdy, + tx: rect2d.transform.matrix.tx, + dtdx: rect2d.transform.matrix.dtdx, + dtdy: rect2d.transform.matrix.dtdy, + ty: rect2d.transform.matrix.ty, + }; + } else { + transform = Mapper3D.IDENTITY_TRANSFORM; + } + return transform; + } + + private getMaxDisplaySize(rects2d: Rectangle[]): Size { + const displays = rects2d.filter((rect2d) => rect2d.isDisplay); + + let maxWidth = 0; + let maxHeight = 0; + if (displays.length > 0) { + maxWidth = Math.max( + ...displays.map((rect2d): number => Math.abs(rect2d.topLeft.x - rect2d.bottomRight.x)) + ); + + maxHeight = Math.max( + ...displays.map((rect2d): number => Math.abs(rect2d.topLeft.y - rect2d.bottomRight.y)) + ); + + } + return { + width: maxWidth, + height: maxHeight + } + } + + private cropOversizedRect(rect3d: Rect3D, maxDisplaySize: Size): Rect3D { + // Arbitrary max size for a rect (2x the maximum display) + let maxDimension = Number.MAX_VALUE; + if (maxDisplaySize.height > 0) { + maxDimension = Math.max(maxDisplaySize.width, maxDisplaySize.height) * 2 + } + + const height = Math.abs(rect3d.topLeft.y - rect3d.bottomRight.y); + const width = Math.abs(rect3d.topLeft.x - rect3d.bottomRight.x); + + if (width > maxDimension) { + rect3d.isOversized = true; + rect3d.topLeft.x = (maxDimension - (maxDisplaySize.width / 2)) * -1, + rect3d.bottomRight.x = maxDimension + } + if (height > maxDimension) { + rect3d.isOversized = true; + rect3d.topLeft.y = (maxDimension - (maxDisplaySize.height / 2)) * -1 + rect3d.bottomRight.y = maxDimension + } + + return rect3d; + } + private computeLabels(rects2d: Rectangle[], rects3d: Rect3D[]): Label3D[] { const labels3d: Label3D[] = []; let labelY = Math.max(...rects3d.map(rect => { - const bottomRight = { - x: rect.center.x + rect.width / 2, - y: rect.center.y + rect.height / 2, - z: rect.center.z, - }; - return this.matMultiply(rect.transform, bottomRight).y; + return this.matMultiply(rect.transform, rect.bottomRight).y; })) + Mapper3D.LABEL_FIRST_Y_OFFSET; rects2d.forEach((rect2d, index) => { @@ -274,27 +299,13 @@ class Mapper3D { const rect3d = rects3d[index]; + const bottomLeft: Point3D = { x: rect3d.topLeft.x, y: rect3d.bottomRight.y, z: rect3d.topLeft.z }; + const topRight: Point3D = { x: rect3d.bottomRight.x, y: rect3d.topLeft.y, z: rect3d.bottomRight.z }; const lineStarts = [ - this.matMultiply(rect3d.transform, { - x: rect3d.center.x - rect3d.width / 2, - y: rect3d.center.y, - z: rect3d.center.z - }), - this.matMultiply(rect3d.transform, { - x: rect3d.center.x + rect3d.width / 2, - y: rect3d.center.y, - z: rect3d.center.z - }), - this.matMultiply(rect3d.transform, { - x: rect3d.center.x, - y: rect3d.center.y - rect3d.width / 2, - z: rect3d.center.z - }), - this.matMultiply(rect3d.transform, { - x: rect3d.center.x, - y: rect3d.center.y + rect3d.width / 2, - z: rect3d.center.z - }) + this.matMultiply(rect3d.transform, rect3d.topLeft), + this.matMultiply(rect3d.transform, rect3d.bottomRight), + this.matMultiply(rect3d.transform, bottomLeft), + this.matMultiply(rect3d.transform, topRight) ]; let maxIndex = 0; for (let i = 1; i < lineStarts.length; i++) { @@ -350,7 +361,7 @@ class Mapper3D { width: 1, height: 1, depth: 1, - center: {x: 0, y: 0, z: 0}, + center: { x: 0, y: 0, z: 0 }, diagonal: Math.sqrt(3) }; } @@ -372,7 +383,7 @@ class Mapper3D { }; rects.forEach(rect => { - const topLeft: Point3D = { + /*const topLeft: Point3D = { x: rect.center.x - rect.width / 2, y: rect.center.y + rect.height / 2, z: rect.center.z @@ -381,9 +392,9 @@ class Mapper3D { x: rect.center.x + rect.width / 2, y: rect.center.y - rect.height / 2, z: rect.center.z - }; - updateMinMaxCoordinates(topLeft); - updateMinMaxCoordinates(bottomRight); + };*/ + updateMinMaxCoordinates(rect.topLeft); + updateMinMaxCoordinates(rect.bottomRight); }); labels.forEach(label => { @@ -412,4 +423,4 @@ class Mapper3D { } } -export {Mapper3D}; +export { Mapper3D }; diff --git a/tools/winscope-ng/src/viewers/components/rects/rects.component.spec.ts b/tools/winscope-ng/src/viewers/components/rects/rects.component.spec.ts index 09bf273fd..4d47e3a41 100644 --- a/tools/winscope-ng/src/viewers/components/rects/rects.component.spec.ts +++ b/tools/winscope-ng/src/viewers/components/rects/rects.component.spec.ts @@ -84,15 +84,14 @@ describe("RectsComponent", () => { ty: 0 } }, - height: 1, - width: 1, isVisible: true, isDisplay: false, ref: null, id: "test-id-1234", displayId: 0, isVirtual: false, - isClickable: false + isClickable: false, + cornerRadius: 0 }; expect(Canvas.prototype.draw).toHaveBeenCalledTimes(0); diff --git a/tools/winscope-ng/src/viewers/components/rects/types3d.ts b/tools/winscope-ng/src/viewers/components/rects/types3d.ts index ce76bf2c0..600e799e1 100644 --- a/tools/winscope-ng/src/viewers/components/rects/types3d.ts +++ b/tools/winscope-ng/src/viewers/components/rects/types3d.ts @@ -35,13 +35,14 @@ export interface Box3D { export interface Rect3D { id: string; - center: Point3D; - width: number; - height: number; + topLeft: Point3D; + bottomRight: Point3D; + cornerRadius: number; darkFactor: number; colorType: ColorType; isClickable: boolean; transform: Transform3D; + isOversized: boolean; } export interface Transform3D { diff --git a/tools/winscope-ng/src/viewers/viewer_surface_flinger/presenter.spec.ts b/tools/winscope-ng/src/viewers/viewer_surface_flinger/presenter.spec.ts index 9ea24d173..20f18f2dd 100644 --- a/tools/winscope-ng/src/viewers/viewer_surface_flinger/presenter.spec.ts +++ b/tools/winscope-ng/src/viewers/viewer_surface_flinger/presenter.spec.ts @@ -75,8 +75,6 @@ describe("PresenterSurfaceFlinger", () => { expect(uiData.rects.length).toBeGreaterThan(0); expect(uiData.rects[0].topLeft).toEqual({x: 0, y: 0}); expect(uiData.rects[0].bottomRight).toEqual({x: 1080, y: 118}); - expect(uiData.rects[0].width).toEqual(1080); - expect(uiData.rects[0].height).toEqual(118); }); it("updates pinned items", () => { diff --git a/tools/winscope-ng/src/viewers/viewer_surface_flinger/presenter.ts b/tools/winscope-ng/src/viewers/viewer_surface_flinger/presenter.ts index 02cb00f3b..c13445462 100644 --- a/tools/winscope-ng/src/viewers/viewer_surface_flinger/presenter.ts +++ b/tools/winscope-ng/src/viewers/viewer_surface_flinger/presenter.ts @@ -114,6 +114,7 @@ export class Presenter { rect.stableId = `Display - ${display.id}`; rect.displayId = display.layerStackId; rect.isDisplay = true; + rect.cornerRadius = 0; rect.isVirtual = display.isVirtual ?? false; rect.transform = { matrix: display.transform.matrix @@ -126,6 +127,7 @@ export class Presenter { .map((it: any) => { const rect = it.rect; rect.displayId = it.stackId; + rect.cornerRadius = it.cornerRadius; if (!this.displayIds.includes(it.stackId)) { this.displayIds.push(it.stackId); } @@ -214,8 +216,6 @@ export class Presenter { const newRect: Rectangle = { topLeft: {x: rect.left, y: rect.top}, bottomRight: {x: rect.right, y: rect.bottom}, - height: rect.height, - width: rect.width, label: rect.label, transform: transform, isVisible: rect.ref?.isVisible ?? false, @@ -224,7 +224,8 @@ export class Presenter { id: rect.stableId ?? rect.ref.stableId, displayId: rect.displayId ?? rect.ref.stackId, isVirtual: rect.isVirtual ?? false, - isClickable: !(rect.isDisplay ?? false) + isClickable: !(rect.isDisplay ?? false), + cornerRadius: rect.cornerRadius }; uiRects.push(newRect); }); diff --git a/tools/winscope-ng/src/viewers/viewer_window_manager/presenter.spec.ts b/tools/winscope-ng/src/viewers/viewer_window_manager/presenter.spec.ts index 529a0bfa5..c7adf95a8 100644 --- a/tools/winscope-ng/src/viewers/viewer_window_manager/presenter.spec.ts +++ b/tools/winscope-ng/src/viewers/viewer_window_manager/presenter.spec.ts @@ -79,8 +79,6 @@ describe("PresenterWindowManager", () => { expect(uiData.rects.length).toBeGreaterThan(0); expect(uiData.rects[0].topLeft).toEqual({x: 0, y: 2326}); expect(uiData.rects[0].bottomRight).toEqual({x: 1080, y: 2400}); - expect(uiData.rects[0].width).toEqual(1080); - expect(uiData.rects[0].height).toEqual(74); }); it("updates pinned items", () => { diff --git a/tools/winscope-ng/src/viewers/viewer_window_manager/presenter.ts b/tools/winscope-ng/src/viewers/viewer_window_manager/presenter.ts index 02488f2f8..0bbd2e1fa 100644 --- a/tools/winscope-ng/src/viewers/viewer_window_manager/presenter.ts +++ b/tools/winscope-ng/src/viewers/viewer_window_manager/presenter.ts @@ -178,8 +178,6 @@ export class Presenter { const newRect: Rectangle = { topLeft: {x: rect.left, y: rect.top}, bottomRight: {x: rect.right, y: rect.bottom}, - height: rect.height, - width: rect.width, label: rect.label, transform: transform, isVisible: rect.ref?.isVisible ?? false, @@ -189,6 +187,7 @@ export class Presenter { displayId: rect.displayId ?? rect.ref.stackId, isVirtual: rect.isVirtual ?? false, isClickable: !rect.isDisplay, + cornerRadius: rect.cornerRadius, }; uiRects.push(newRect); });