sepdek February 16, 2026
Illustration of ray picking and a selected annotation region on a ceramic vessel mesh.

A point selected on a 3D surface is a geometric location. An annotation saying “possible repair” is an interpretation. Linking the two requires more than saving a screen coordinate, because the viewer can move, the mesh can change and the interpretation can later be revised.

A useful annotation system should preserve both the selected region and the context in which the statement was made.

Research context

Towards complete digital twins in cultural heritage with ART3mis 3D artifacts annotator, by Dimitrios Karamatskos, Vasileios Arampatzakis, Vasileios Sevetlidis, Stavros Nousias, Athanasios Kalogeras, Christos Koulamas, Aris Lalos and George Pavlidis, describes a web-based 3D annotation tool and its use of the W3C Web Annotation model. The record identifies the work as presented at EuroMed 2022, while the arXiv deposit is 13 February 2026. These are different dates. ART3mis should also not be confused with the separately named ARTEMIS project.

The geometry below is an independent teaching example, not an extract from the ART3mis implementation.

From a click to a ray

A screen click determines a viewing ray once the camera and viewport are known. In object coordinates, write the ray as:

r(t)=o+td,t≥0.

Here o is the origin and d is the direction. In a perspective camera, the ray construction uses the inverse projection and view transforms. If the displayed mesh has its own transform, that must also be accounted for. Saving only a pixel coordinate loses this context.

To keep the arithmetic visible, use a triangle with vertices A = (0,0,0), B = (1,0,0), C = (0,1,0). A downward ray from (0.25,0.25,1) intersects its plane at (0.25,0.25,0). That point lies inside the triangle.

Barycentric coordinates describe a position within a face

A point on the triangle can be expressed as:

p=αA+βB+γC,α+β+γ=1.

For the example, the weights are (0.5, 0.25, 0.25). Nonnegative weights place the point inside or on the triangle. The companion calculation solves for the intersection and checks the ray parameter, weights and reconstructed position.

A = np.array([0., 0., 0.])
B = np.array([1., 0., 0.])
C = np.array([0., 1., 0.])
p = .5 * A + .25 * B + .25 * C
assert np.allclose(p, [.25, .25, 0.])

Barycentric coordinates are tied to a face and its vertex order. If a mesh is remeshed, face 17 may no longer identify the same surface region. A robust record therefore needs the model identity and version, not merely a face number.

A region is more than one point

A conservator may want to mark a crack, painted area or treatment region. That can require a set of faces, a surface polygon, an image-based selector linked to the model, or another documented representation. The choice should follow the task and the exchange format used by the application.

It is useful to preserve enough information to revisit the selection, but additional representations can disagree. A saved world-space point and a face-based selection should not silently identify different places after a transform or model update. Validation should detect that disagreement.

Separate the statement from its target

The W3C Web Annotation Data Model distinguishes an annotation’s body from its target. A simple teaching record can attach a textual comment to an entire model resource. A precise 3D subregion requires an additional selector convention understood by the participating applications.

The companion JSON uses a whole-resource target for that reason. It does not invent a fragment such as #face=17 and claim that every W3C-compatible viewer knows how to interpret it. Interoperability needs agreement on the region representation as well as agreement on the outer JSON structure.

Preserve uncertainty and revision history

“Possible repair” should remain distinguishable from “repair confirmed by examination.” Record the creator, date, evidence and status of the interpretation. A later reviewer may disagree without changing the geometric observation that a particular region was selected.

For exchange tests, I would check whether a second application can identify the exact model version, recover the intended target and preserve the annotation’s wording and provenance. A successful screenshot is a useful visual check, but it is not a complete interchange test.

Complete reproducible example

This standalone example uses fictional data or prescribed mathematical objects; it is not a reproduction of the source paper’s experiments.

"""Synthetic ray/triangle intersection with explicit degeneracy checks."""
import json
import numpy as np
A=np.array([0.,0.,0.]); B=np.array([1.,0.,0.]); C=np.array([0.,1.,0.])
o=np.array([.25,.25,1.]); d=np.array([0.,0.,-1.])
# o + t*d = A + beta*(B-A) + gamma*(C-A)
M=np.column_stack((d,-(B-A),-(C-A)))
if abs(np.linalg.det(M))<1e-12: raise ValueError("Degenerate or parallel configuration")
t,beta,gamma=np.linalg.solve(M,A-o)
alpha=1-beta-gamma
inside=t>=0 and min(alpha,beta,gamma)>=-1e-12
point=o+t*d
assert inside and np.allclose(point,[.25,.25,0])
assert np.allclose(point,alpha*A+beta*B+gamma*C)
annotation={"@context":"http://www.w3.org/ns/anno.jsonld", "id":"https://example.org/annotation/1", "type":"Annotation", "motivation":"commenting", "body":{"type":"TextualBody","value":"Possible repair; fictional teaching comment.","format":"text/plain"}, "target":"https://example.org/models/object-1-v1"}
assert annotation["target"].endswith("object-1-v1")
print(json.dumps({"t":float(t),"barycentric":[float(alpha),float(beta),float(gamma)],"point":point.tolist(),"whole_resource_annotation":annotation},indent=2))

Discussion

comments