Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@next2d/player",
"version": "2.7.0",
"version": "2.8.0",
"description": "Experience the fast and beautiful anti-aliased rendering of WebGL. You can create rich, interactive graphics, cross-platform applications and games without worrying about browser or device compatibility.",
"author": "Toshiyuki Ienaga<ienaga@next2d.app> (https://github.com/ienaga/)",
"license": "MIT",
Expand Down
243 changes: 243 additions & 0 deletions packages/display/src/Shape/usecase/ShapeHitTestUseCase.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,4 +180,247 @@ describe("ShapeHitTestUseCase.js test", () =>

expect(typeof result).toBe("boolean");
});

it("execute test case11 - hit inside shape without buffer (default rectangle path)", () =>
{
const shape = new Shape();
shape.graphics.xMin = 0;
shape.graphics.yMin = 0;
shape.graphics.xMax = 100;
shape.graphics.yMax = 100;

const matrix = new Float32Array([1, 0, 0, 1, 0, 0]);
const hitObject = { x: 50, y: 50 };

const result = execute(shape, context, matrix, hitObject);

expect(typeof result).toBe("boolean");
});

it("execute test case12 - hit outside shape without buffer", () =>
{
const shape = new Shape();
shape.graphics.xMin = 0;
shape.graphics.yMin = 0;
shape.graphics.xMax = 100;
shape.graphics.yMax = 100;

const matrix = new Float32Array([1, 0, 0, 1, 0, 0]);
const hitObject = { x: 150, y: 150 };

const result = execute(shape, context, matrix, hitObject);

expect(result).toBe(false);
});

it("execute test case13 - hit on edge of shape without buffer", () =>
{
const shape = new Shape();
shape.graphics.xMin = 0;
shape.graphics.yMin = 0;
shape.graphics.xMax = 100;
shape.graphics.yMax = 100;

const matrix = new Float32Array([1, 0, 0, 1, 0, 0]);
const hitObject = { x: 0, y: 0 };

const result = execute(shape, context, matrix, hitObject);

expect(typeof result).toBe("boolean");
});

it("execute test case14 - hit with shape transformation (rawMatrix)", () =>
{
const shape = new Shape();
shape.graphics.xMin = 0;
shape.graphics.yMin = 0;
shape.graphics.xMax = 50;
shape.graphics.yMax = 50;
shape.x = 50;
shape.y = 50;

const matrix = new Float32Array([1, 0, 0, 1, 0, 0]);
const hitObject = { x: 75, y: 75 };

const result = execute(shape, context, matrix, hitObject);

expect(typeof result).toBe("boolean");
});

it("execute test case15 - hit with scaled shape transformation", () =>
{
const shape = new Shape();
shape.graphics.xMin = 0;
shape.graphics.yMin = 0;
shape.graphics.xMax = 50;
shape.graphics.yMax = 50;
shape.scaleX = 2;
shape.scaleY = 2;

const matrix = new Float32Array([1, 0, 0, 1, 0, 0]);
const hitObject = { x: 75, y: 75 };

const result = execute(shape, context, matrix, hitObject);

expect(typeof result).toBe("boolean");
});

it("execute test case16 - hit with rotated shape", () =>
{
const shape = new Shape();
shape.graphics.xMin = 0;
shape.graphics.yMin = 0;
shape.graphics.xMax = 100;
shape.graphics.yMax = 100;
shape.rotation = 45;

const matrix = new Float32Array([1, 0, 0, 1, 0, 0]);
const hitObject = { x: 50, y: 50 };

const result = execute(shape, context, matrix, hitObject);

expect(typeof result).toBe("boolean");
});

it("execute test case17 - hit with combined parent matrix and shape transformation", () =>
{
const shape = new Shape();
shape.graphics.xMin = 0;
shape.graphics.yMin = 0;
shape.graphics.xMax = 50;
shape.graphics.yMax = 50;
shape.x = 10;
shape.y = 10;

const parentMatrix = new Float32Array([1, 0, 0, 1, 100, 100]);
const hitObject = { x: 125, y: 125 };

const result = execute(shape, context, parentMatrix, hitObject);

expect(typeof result).toBe("boolean");
});

it("execute test case18 - hit with graphics buffer", () =>
{
const shape = new Shape();
shape.graphics.beginFill(0xFF0000);
shape.graphics.drawRect(0, 0, 100, 100);
shape.graphics.endFill();

const matrix = new Float32Array([1, 0, 0, 1, 0, 0]);
const hitObject = { x: 50, y: 50 };

const result = execute(shape, context, matrix, hitObject);

expect(typeof result).toBe("boolean");
});

it("execute test case19 - hit outside with graphics buffer", () =>
{
const shape = new Shape();
shape.graphics.beginFill(0xFF0000);
shape.graphics.drawRect(0, 0, 100, 100);
shape.graphics.endFill();

const matrix = new Float32Array([1, 0, 0, 1, 0, 0]);
const hitObject = { x: 150, y: 150 };

const result = execute(shape, context, matrix, hitObject);

expect(typeof result).toBe("boolean");
});

it("execute test case20 - hit with line stroke graphics buffer", () =>
{
const shape = new Shape();
shape.graphics.lineStyle(5, 0x000000);
shape.graphics.moveTo(0, 0);
shape.graphics.lineTo(100, 100);

const matrix = new Float32Array([1, 0, 0, 1, 0, 0]);
const hitObject = { x: 50, y: 50 };

const result = execute(shape, context, matrix, hitObject);

expect(typeof result).toBe("boolean");
});

it("execute test case21 - hit with circle graphics buffer", () =>
{
const shape = new Shape();
shape.graphics.beginFill(0x00FF00);
shape.graphics.drawCircle(50, 50, 50);
shape.graphics.endFill();

const matrix = new Float32Array([1, 0, 0, 1, 0, 0]);
const hitObject = { x: 50, y: 50 };

const result = execute(shape, context, matrix, hitObject);

expect(typeof result).toBe("boolean");
});

it("execute test case22 - handles negative coordinates", () =>
{
const shape = new Shape();
shape.graphics.xMin = -50;
shape.graphics.yMin = -50;
shape.graphics.xMax = 50;
shape.graphics.yMax = 50;

const matrix = new Float32Array([1, 0, 0, 1, 0, 0]);
const hitObject = { x: 0, y: 0 };

const result = execute(shape, context, matrix, hitObject);

expect(typeof result).toBe("boolean");
});

it("execute test case23 - handles fractional coordinates", () =>
{
const shape = new Shape();
shape.graphics.xMin = 0.5;
shape.graphics.yMin = 0.5;
shape.graphics.xMax = 100.5;
shape.graphics.yMax = 100.5;

const matrix = new Float32Array([1, 0, 0, 1, 0, 0]);
const hitObject = { x: 50.25, y: 50.25 };

const result = execute(shape, context, matrix, hitObject);

expect(typeof result).toBe("boolean");
});

it("execute test case24 - handles very small shape", () =>
{
const shape = new Shape();
shape.graphics.xMin = 0;
shape.graphics.yMin = 0;
shape.graphics.xMax = 1;
shape.graphics.yMax = 1;

const matrix = new Float32Array([1, 0, 0, 1, 0, 0]);
const hitObject = { x: 0.5, y: 0.5 };

const result = execute(shape, context, matrix, hitObject);

expect(typeof result).toBe("boolean");
});

it("execute test case25 - handles very large shape", () =>
{
const shape = new Shape();
shape.graphics.xMin = 0;
shape.graphics.yMin = 0;
shape.graphics.xMax = 10000;
shape.graphics.yMax = 10000;

const matrix = new Float32Array([1, 0, 0, 1, 0, 0]);
const hitObject = { x: 5000, y: 5000 };

const result = execute(shape, context, matrix, hitObject);

expect(typeof result).toBe("boolean");
});
});
17 changes: 14 additions & 3 deletions packages/display/src/Shape/usecase/ShapeHitTestUseCase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,20 @@ export const execute = (
tMatrix[3], tMatrix[4], tMatrix[5]
);

const hit = graphicsHitTestService(
hit_context, graphics.buffer, hit_object
);
let hit = false;
if (graphics.buffer.length) {
hit = graphicsHitTestService(
hit_context, graphics.buffer, hit_object
);
} else {
hit_context.moveTo(0, 0);
hit_context.lineTo(width, 0);
hit_context.lineTo(width, height);
hit_context.lineTo(0, height);
hit_context.lineTo(0, 0);
Comment on lines +51 to +55
Copy link

Copilot AI Nov 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fallback rectangle path is using incorrect coordinates. The path starts at (0, 0) and extends to (width, height), but it should start at (xMin, yMin) and extend to (xMax, yMax) to correctly represent the shape's bounds.

For example, if a shape has xMin = -50, yMin = -50, xMax = 50, yMax = 50, the current code would create a rectangle from (0, 0) to (100, 100), but it should create a rectangle from (-50, -50) to (50, 50).

Suggested fix:

hit_context.moveTo(graphics.xMin, graphics.yMin);
hit_context.lineTo(graphics.xMax, graphics.yMin);
hit_context.lineTo(graphics.xMax, graphics.yMax);
hit_context.lineTo(graphics.xMin, graphics.yMax);
hit_context.lineTo(graphics.xMin, graphics.yMin);
Suggested change
hit_context.moveTo(0, 0);
hit_context.lineTo(width, 0);
hit_context.lineTo(width, height);
hit_context.lineTo(0, height);
hit_context.lineTo(0, 0);
hit_context.moveTo(graphics.xMin, graphics.yMin);
hit_context.lineTo(graphics.xMax, graphics.yMin);
hit_context.lineTo(graphics.xMax, graphics.yMax);
hit_context.lineTo(graphics.xMin, graphics.yMax);
hit_context.lineTo(graphics.xMin, graphics.yMin);

Copilot uses AI. Check for mistakes.

hit = hit_context.isPointInPath(hit_object.x, hit_object.y);
}

if (tMatrix !== matrix) {
Matrix.release(tMatrix);
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { Next2D } from "@next2d/core";

if (!("next2d" in window)) {
console.log("%c Next2D Player %c 2.7.0 %c https://next2d.app",
console.log("%c Next2D Player %c 2.8.0 %c https://next2d.app",
"color: #fff; background: #5f5f5f",
"color: #fff; background: #4bc729",
"");
Expand Down
Loading