Skip to content

Commit

Permalink
fix: call updateWorldMatrix when accessing object.matrixWorld
Browse files Browse the repository at this point in the history
  • Loading branch information
bbohlender committed Nov 3, 2024
1 parent bfcef6d commit 1a43d0d
Show file tree
Hide file tree
Showing 12 changed files with 83 additions and 41 deletions.
67 changes: 41 additions & 26 deletions packages/pointer-events/src/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,42 +126,40 @@ export class PointerEvent<E extends NativeEvent = globalThis.PointerEvent>
return this.currentObject
}

constructor(
public readonly type: keyof PointerEventsMap,
public readonly bubbles: boolean,
nativeEvent: E,
protected internalPointer: Pointer,
protected readonly intersection: ThreeIntersection,
public readonly camera: Camera,
public readonly currentObject: Object3D = intersection.object,
public readonly object: Object3D = currentObject,
private readonly propagationState: { stopped: boolean; stoppedImmediate: boolean } = {
stopped: !bubbles,
stoppedImmediate: false,
},
) {
super(nativeEvent)
}

private _pointer: Vector2 | undefined
get pointer(): Vector2 {
helperVector.copy(this.intersection.point).project(this.camera)
return new Vector2(helperVector.x, helperVector.y)
if (this._pointer == null) {
helperVector.copy(this.intersection.point).project(this.camera)
this._pointer = new Vector2(helperVector.x, helperVector.y)
}
return this._pointer
}

private _ray: Ray | undefined
get ray(): Ray {
const ray = new Ray()
ray.origin.setFromMatrixPosition(this.camera.matrixWorld)
ray.lookAt(this.point)
return ray
if (this._ray == null) {
this._ray = new Ray()
this._ray.origin.setFromMatrixPosition(this.camera.matrixWorld)
this._ray.lookAt(this.point)
}
return this._ray
}

private _intersections: Array<Intersection> = []
get intersections(): Intersection[] {
return [{ ...this.intersection, eventObject: this.currentObject }]
if (this._intersections == null) {
this._intersections = [{ ...this.intersection, eventObject: this.currentObject }]
}
return this._intersections
}

private _unprojectedPoint: Vector3 | undefined
get unprojectedPoint(): Vector3 {
const p = this.pointer
return new Vector3(p.x, p.y, 0).unproject(this.camera)
if (this._unprojectedPoint == null) {
const p = this.pointer
this._unprojectedPoint = new Vector3(p.x, p.y, 0).unproject(this.camera)
}
return this._unprojectedPoint
}

get stopped(): boolean {
Expand All @@ -176,6 +174,23 @@ export class PointerEvent<E extends NativeEvent = globalThis.PointerEvent>
throw new Error(`not supported`)
}

constructor(
public readonly type: keyof PointerEventsMap,
public readonly bubbles: boolean,
nativeEvent: E,
protected internalPointer: Pointer,
protected readonly intersection: ThreeIntersection,
public readonly camera: Camera,
public readonly currentObject: Object3D = intersection.object,
public readonly object: Object3D = currentObject,
private readonly propagationState: { stopped: boolean; stoppedImmediate: boolean } = {
stopped: !bubbles,
stoppedImmediate: false,
},
) {
super(nativeEvent)
}

stopPropagation(): void {
this.propagationState.stopped = true
}
Expand Down
1 change: 0 additions & 1 deletion packages/pointer-events/src/forward.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { NativeEvent, NativeWheelEvent, PointerEvent } from './event.js'
import { CameraRayIntersector } from './intersections/ray.js'
import { generateUniquePointerId } from './pointer/index.js'
import { IntersectionOptions } from './intersections/index.js'
import { getClosestUV } from './utils.js'

export type ForwardablePointerEvent = { pointerId: number; pointerType: string; pointerState?: any } & NativeEvent

Expand Down
6 changes: 5 additions & 1 deletion packages/pointer-events/src/intersections/lines.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,16 @@ export class LinesIntersector implements Intersector {
lineHelper.set(linePoints[details.lineIndex], linePoints[details.lineIndex + 1]).applyMatrix4(this.fromMatrixWorld)

const point = lineHelper.at(details.distanceOnLine / lineHelper.distance(), new Vector3())
computeIntersectionWorldPlane(planeHelper, intersection, object)
intersection.object.updateWorldMatrix(true, false)
computeIntersectionWorldPlane(planeHelper, intersection, intersection.object.matrixWorld)
const pointOnFace = rayHelper.intersectPlane(planeHelper, new Vector3()) ?? point
let uv = intersection.uv
if (intersection.object instanceof Mesh && getClosestUV(point2Helper, point, intersection.object)) {
uv = point2Helper.clone()
}
return {
...intersection,
object,
uv,
pointOnFace,
point,
Expand Down Expand Up @@ -168,6 +170,8 @@ export class LinesIntersector implements Intersector {
distance += this.raycasters[i].far
}

intersection.object.updateWorldMatrix(true, false)

//TODO: consider maxLength
return Object.assign(intersection, {
details: {
Expand Down
10 changes: 7 additions & 3 deletions packages/pointer-events/src/intersections/ray.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ export class RayIntersector implements Intersector {
if (!this.prepareTransformation()) {
return intersection
}
computeIntersectionWorldPlane(planeHelper, intersection, object)
intersection.object.updateWorldMatrix(true, false)
computeIntersectionWorldPlane(planeHelper, intersection, intersection.object.matrixWorld)
const { ray } = this.raycaster
const pointOnFace = ray.intersectPlane(planeHelper, new Vector3()) ?? intersection.point
const point = ray.direction.clone().multiplyScalar(intersection.distance).add(ray.origin)
Expand Down Expand Up @@ -127,6 +128,7 @@ export class RayIntersector implements Intersector {
pointerQuaternion,
)
}
intersection.object.updateWorldMatrix(true, false)
return Object.assign(intersection, {
details: {
type: 'ray' as const,
Expand Down Expand Up @@ -180,7 +182,8 @@ export class CameraRayIntersector implements Intersector {
return intersection
}

computeIntersectionWorldPlane(this.viewPlane, intersection, object)
intersection.object.updateWorldMatrix(true, false)
computeIntersectionWorldPlane(this.viewPlane, intersection, intersection.object.matrixWorld)
let uv = intersection.uv
if (intersection.object instanceof Mesh && getClosestUV(point2Helper, point, intersection.object)) {
uv = point2Helper.clone()
Expand All @@ -201,8 +204,8 @@ export class CameraRayIntersector implements Intersector {
if (from == null) {
return false
}
from.matrixWorld.decompose(this.fromPosition, this.fromQuaternion, scaleHelper)
from.updateWorldMatrix(true, false)
from.matrixWorld.decompose(this.fromPosition, this.fromQuaternion, scaleHelper)
this.raycaster.setFromCamera(this.coords, from)
this.viewPlane.setFromNormalAndCoplanarPoint(from.getWorldDirection(directionHelper), this.raycaster.ray.origin)
return true
Expand Down Expand Up @@ -233,6 +236,7 @@ export class CameraRayIntersector implements Intersector {
)
}

intersection.object.updateWorldMatrix(true, false)
invertedMatrixHelper.copy(intersection.object.matrixWorld).invert()

return Object.assign(intersection, {
Expand Down
5 changes: 4 additions & 1 deletion packages/pointer-events/src/intersections/sphere.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ export class SphereIntersector implements Intersector {
//apply quaternion offset to old inputDevicePosition-point offset and add to new inputDevicePosition
const point = oldInputDevicePointOffset.clone().applyQuaternion(inputDeviceQuaternionOffset).add(this.fromPosition)

computeIntersectionWorldPlane(planeHelper, intersection, object)
intersection.object.updateWorldMatrix(true, false)
computeIntersectionWorldPlane(planeHelper, intersection, intersection.object.matrixWorld)

const pointOnFace = planeHelper.projectPoint(this.fromPosition, new Vector3())

Expand Down Expand Up @@ -134,6 +135,8 @@ export class SphereIntersector implements Intersector {
}
}

intersection.object.updateWorldMatrix(true, false)

return Object.assign(intersection, {
details: {
type: 'sphere' as const,
Expand Down
10 changes: 7 additions & 3 deletions packages/pointer-events/src/intersections/utils.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import { Plane, Intersection as ThreeIntersection, Object3D, Vector3, Ray, Quaternion } from 'three'
import { Plane, Intersection as ThreeIntersection, Object3D, Vector3, Ray, Quaternion, Matrix4 } from 'three'
import { Intersection, IntersectionOptions } from './index.js'
import { AllowedPointerEventsType, Pointer, type AllowedPointerEvents } from '../pointer.js'
import { getVoidObject, VoidObjectCollider } from './intersector.js'
import { listenerNames } from '../event.js'

export function computeIntersectionWorldPlane(target: Plane, intersection: Intersection, object: Object3D): boolean {
export function computeIntersectionWorldPlane(
target: Plane,
intersection: Intersection,
objectMatrixWorld: Matrix4,
): boolean {
const normal = intersection.normal ?? intersection.face?.normal
if (normal == null) {
return false
}
target.setFromNormalAndCoplanarPoint(normal, intersection.localPoint)
target.applyMatrix4(object.matrixWorld)
target.applyMatrix4(objectMatrixWorld)
return true
}

Expand Down
16 changes: 10 additions & 6 deletions packages/pointer-events/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import { BufferAttribute, Matrix4, Mesh, Object3D, Triangle, Vector2, Vector3 } from 'three'
import { PointerEventsMap } from './event.js'

export function updateAndCheckWorldTransformation({ transformReady, parent, matrix, matrixWorld }: Object3D): boolean {
if (transformReady === false) {
export function updateAndCheckWorldTransformation(object: Object3D): boolean {
if (object.transformReady === false) {
return false
}
if (parent == null) {
if (object.parent == null) {
object.matrixWorld.copy(object.matrix)
return true
}
if (!updateAndCheckWorldTransformation(parent)) {
if (!updateAndCheckWorldTransformation(object.parent)) {
return false
}
matrixWorld.multiplyMatrices(parent.matrixWorld, matrix)
//we can just use parent.matrixWorld here because we already executed `updateAndCheckWorldTransformation` before which has updated parent.matrixWorld
object.matrixWorld.multiplyMatrices(object.parent.matrixWorld, object.matrix)
return true
}

Expand All @@ -24,6 +25,9 @@ const pointHelper = new Vector3()
const inverseMatrix = new Matrix4()
const localPointHelper = new Vector3()

/**
* @requires that `mesh.updateWorldMatrix(true, false)` was executed beforehand
*/
export function getClosestUV(target: Vector2, point: Vector3, mesh: Mesh): boolean {
localPointHelper.copy(point).applyMatrix4(inverseMatrix.copy(mesh.matrixWorld).invert())
const uv = mesh.geometry.attributes.uv
Expand Down
2 changes: 2 additions & 0 deletions packages/react/xr/src/deprecated/ray-grab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const RayGrab = forwardRef<Group, ComponentPropsWithoutRef<typeof Interac
if (!group || !controller) return

group.applyMatrix4(previousTransform)
controller.updateWorldMatrix(true, false)
group.applyMatrix4(controller.matrixWorld)
group.updateMatrixWorld()

Expand All @@ -38,6 +39,7 @@ export const RayGrab = forwardRef<Group, ComponentPropsWithoutRef<typeof Interac
e.target.object != null
) {
grabbingController.current = e.target.object
e.target.object.updateWorldMatrix(true, false)
previousTransform.copy(e.target.object.matrixWorld).invert()
onSelectStart?.(e)
}
Expand Down
1 change: 1 addition & 0 deletions packages/xr/src/anchor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ export async function requestXRAnchor(store: XRStore<any>, options: XRAnchorOpti
const { worldPosition, worldQuaternion } = options
if (origin != null) {
//compute vectorHelper and quaternionHelper in the local space of the origin
origin.updateWorldMatrix(true, false)
matrixHelper1.copy(origin.matrixWorld).invert()
matrixHelper2.compose(worldPosition, worldQuaternion, OneVector).multiply(matrixHelper1)
matrixHelper2.decompose(positionHelper, quaternionHelper, vectorHelper)
Expand Down
1 change: 1 addition & 0 deletions packages/xr/src/hit-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ function computeWorldMatrixFromXRHitTestResult(
//target = ObjectMatrixWorld? * HitTestMatrix
target.fromArray(pose.transform.matrix)
if (object != null) {
object.updateWorldMatrix(true, false)
target.premultiply(object.matrixWorld)
}
return true
Expand Down
4 changes: 4 additions & 0 deletions packages/xr/src/space.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,14 @@ export function getSpaceFromAncestors(
}

function computeOriginReferenceSpaceOffset(object: Object3D, origin: Object3D | undefined, target: Matrix4): void {
object.updateWorldMatrix(true, false)
if (origin == null) {
target.copy(object.matrixWorld)
return
}
origin.updateWorldMatrix(true, false)
//origin * offset = space <=>
//target = origin.matrixWorld-1 * object.matrixWorld
target.copy(origin.matrixWorld).invert().multiply(object.matrixWorld)
}

Expand Down
1 change: 1 addition & 0 deletions packages/xr/src/teleport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ const quaternionHelper = new Quaternion()
* @param rayGroup must be placed directly into the scene
*/
export function syncTeleportPointerRayGroup(space: Object3D, rayGroup: Object3D, deltaTimeMs: number) {
space.updateWorldMatrix(true, false)
space.matrixWorld.decompose(rayGroup.position, quaternionHelper, rayGroup.scale)
eulerHelper.setFromQuaternion(quaternionHelper)
eulerHelper.z = 0
Expand Down

0 comments on commit 1a43d0d

Please sign in to comment.