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
24 changes: 15 additions & 9 deletions packages/model-viewer/src/features/annotation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

import {Matrix3, Matrix4} from 'three';

import ModelViewerElementBase, {$needsRender, $scene, $tick, toVector3D, Vector3D} from '../model-viewer-base.js';
import ModelViewerElementBase, {$needsRender, $scene, $tick, toVector3D, Vector3D, toVector2D, Vector2D} from '../model-viewer-base.js';
import {Hotspot, HotspotConfiguration} from '../three-components/Hotspot.js';
import {Constructor} from '../utilities.js';

Expand All @@ -34,7 +34,7 @@ const worldToModelNormal = new Matrix3();
export declare interface AnnotationInterface {
updateHotspot(config: HotspotConfiguration): void;
positionAndNormalFromPoint(pixelX: number, pixelY: number):
{position: Vector3D, normal: Vector3D}|null
{position: Vector3D, normal: Vector3D, uv: Vector2D | null}|null
}

/**
Expand Down Expand Up @@ -127,14 +127,15 @@ export const AnnotationMixin = <T extends Constructor<ModelViewerElementBase>>(
}

/**
* This method returns the model position and normal of the point on the
* mesh corresponding to the input pixel coordinates given relative to the
* model-viewer element. The position and normal are returned as strings in
* the format suitable for putting in a hotspot's data-position and
* data-normal attributes. If the mesh is not hit, the result is null.
* This method returns the model position, normal and texture coordinate
* of the point on the mesh corresponding to the input pixel coordinates
* given relative to the model-viewer element. The position and normal
* are returned as strings in the format suitable for putting in a
* hotspot's data-position and data-normal attributes. If the mesh is
* not hit, the result is null.
*/
positionAndNormalFromPoint(pixelX: number, pixelY: number):
{position: Vector3D, normal: Vector3D}|null {
{position: Vector3D, normal: Vector3D, uv: Vector2D | null}|null {
const scene = this[$scene];
const ndcPosition = scene.getNDC(pixelX, pixelY);

Expand All @@ -150,7 +151,12 @@ export const AnnotationMixin = <T extends Constructor<ModelViewerElementBase>>(
const normal =
toVector3D(hit.normal.applyNormalMatrix(worldToModelNormal));

return {position: position, normal: normal};
let uv = null;
if (hit.uv != null){
uv = toVector2D(hit.uv);
}

return {position: position, normal: normal, uv: uv};
}

private[$addHotspot](node: Node) {
Expand Down
2 changes: 1 addition & 1 deletion packages/model-viewer/src/features/scene-graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export interface SceneGraphInterface {
* objects were intersected.
* @param pixelX X coordinate of the mouse.
* @param pixelY Y coordinate of the mouse.
* @returns a material, if no intersection is made than null is returned.
* @returns a material, if no intersection is made then null is returned.
*/
materialFromPoint(pixelX: number, pixelY: number): Material|null;
}
Expand Down
18 changes: 17 additions & 1 deletion packages/model-viewer/src/model-viewer-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

import {property} from 'lit-element';
import {UpdatingElement} from 'lit-element/lib/updating-element';
import {Event as ThreeEvent, Vector3} from 'three';
import {Event as ThreeEvent, Vector3, Vector2} from 'three';

import {HAS_INTERSECTION_OBSERVER, HAS_RESIZE_OBSERVER} from './constants.js';
import {makeTemplate} from './template.js';
Expand Down Expand Up @@ -83,6 +83,22 @@ export const toVector3D = (v: Vector3) => {
};
};

export interface Vector2D {
u: number
v: number
toString(): string
}

export const toVector2D = (v: Vector2) => {
return {
u: v.x,
v: v.y,
toString() {
return `${this.u} ${this.v}`;
}
};
};

interface ToBlobOptions {
mimeType?: string, qualityArgument?: number, idealAspect?: boolean
}
Expand Down
17 changes: 14 additions & 3 deletions packages/model-viewer/src/test/features/annotation-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import {Vector3} from 'three';

import {AnnotationInterface, AnnotationMixin} from '../../features/annotation';
import ModelViewerElementBase, {$needsRender, $scene, Vector3D} from '../../model-viewer-base';
import ModelViewerElementBase, {$needsRender, $scene, Vector2D, Vector3D} from '../../model-viewer-base';
import {Hotspot} from '../../three-components/Hotspot.js';
import {ModelScene} from '../../three-components/ModelScene';
import {timePasses, waitForEvent} from '../../utilities';
Expand Down Expand Up @@ -49,6 +49,11 @@ const closeToVector3 = (a: Vector3D, b: Vector3) => {
expect(a.z).to.be.closeTo(b.z, delta);
};

const withinRange = (a: Vector2D, start: number, finish: number) => {
expect(a.u).to.be.within(start, finish);
expect(a.v).to.be.within(start, finish);
}

suite('ModelViewerElementBase with AnnotationMixin', () => {
let nextId = 0;
let tagName: string;
Expand Down Expand Up @@ -205,9 +210,12 @@ suite('ModelViewerElementBase with AnnotationMixin', () => {
const hitResult =
element.positionAndNormalFromPoint(width / 2, height / 2);
expect(hitResult).to.be.ok;
const {position, normal} = hitResult!;
const {position, normal, uv} = hitResult!;
closeToVector3(position, new Vector3(0, 0, 0.5));
closeToVector3(normal, new Vector3(0, 0, 1));
if(uv != null){
withinRange(uv, 0, 1);
}
});

test('gets expected hit result when turned', () => {
Expand All @@ -216,9 +224,12 @@ suite('ModelViewerElementBase with AnnotationMixin', () => {
const hitResult =
element.positionAndNormalFromPoint(width / 2, height / 2);
expect(hitResult).to.be.ok;
const {position, normal} = hitResult!;
const {position, normal, uv} = hitResult!;
closeToVector3(position, new Vector3(0.5, 0, 0));
closeToVector3(normal, new Vector3(1, 0, 0));
if(uv != null){
withinRange(uv, 0, 1);
}
});
});
});
15 changes: 10 additions & 5 deletions packages/model-viewer/src/three-components/ModelScene.ts
Original file line number Diff line number Diff line change
Expand Up @@ -655,12 +655,13 @@ export class ModelScene extends Scene {
}

/**
* This method returns the world position and model-space normal of the point
* on the mesh corresponding to the input pixel coordinates given relative to
* the model-viewer element. If the mesh is not hit, the result is null.
* This method returns the world position, model-space normal and texture
* coordinate of the point on the mesh corresponding to the input pixel
* coordinates given relative to the model-viewer element. If the mesh
* is not hit, the result is null.
*/
positionAndNormalFromPoint(ndcPosition: Vector2, object: Object3D = this):
{position: Vector3, normal: Vector3}|null {
{position: Vector3, normal: Vector3, uv: Vector2 | null}|null {
this.raycaster.setFromCamera(ndcPosition, this.getCamera());
const hits = this.raycaster.intersectObject(object, true);

Expand All @@ -673,10 +674,14 @@ export class ModelScene extends Scene {
return null;
}

if (hit.uv == null) {
return {position: hit.point, normal: hit.face.normal, uv: null};
}

hit.face.normal.applyNormalMatrix(
new Matrix3().getNormalMatrix(hit.object.matrixWorld));

return {position: hit.point, normal: hit.face.normal};
return {position: hit.point, normal: hit.face.normal, uv: hit.uv};
}

/**
Copy link
Contributor

Choose a reason for hiding this comment

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

Forgot to delete?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Indeed

Expand Down
2 changes: 1 addition & 1 deletion packages/modelviewer.dev/data/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -714,7 +714,7 @@
{
"name": "positionAndNormalFromPoint(clientX, clientY)",
"htmlName": "positionAndNormalFromPoint",
"description": "Returns the world position and normal of the point on the mesh corresponding to the input pixel coordinates given relative to the screen. The position and normal are returned as Vector3D, which has a method toString() that outputs a format suitable for putting in a hotspot's <span class='attribute'>data-position</span> and <span class='attribute'>data-normal</span> attributes. The function returns null if no object is hit.",
"description": "Returns the world position, normal and texture coordinate of the point on the mesh corresponding to the input pixel coordinates given relative to the screen. The position and normal are returned as Vector3D, which has a method toString() that outputs a format suitable for putting in a hotspot's <span class='attribute'>data-position</span> and <span class='attribute'>data-normal</span> attributes. The texture coordinate is returned as Vector2D, which has also a own toString() method. The function returns null if no object is hit.",
"links": [
"<a href=\"../examples/stagingandcameras/#panning\"><span class='attribute'>positionAndNormalFromPoint</span> example</a>"
]
Expand Down