Skip to content

Commit 3cb3fb3

Browse files
committed
prevent plant icons from intersecting raycasting when adding objects
1 parent b406ea3 commit 3cb3fb3

2 files changed

Lines changed: 64 additions & 3 deletions

File tree

frontend/three_d_garden/garden/__tests__/plant_instances_test.tsx

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,12 @@ import { fireEvent, render } from "@testing-library/react";
2222
import { clone } from "lodash";
2323
import { useFrame } from "@react-three/fiber";
2424
import { useTexture } from "@react-three/drei";
25-
import { Quaternion } from "three";
25+
import {
26+
InstancedMesh as ThreeInstancedMesh,
27+
Quaternion,
28+
type Intersection,
29+
type Raycaster,
30+
} from "three";
2631
import { fakePlant } from "../../../__test_support__/fake_state/resources";
2732
import { INITIAL } from "../../config";
2833
import {
@@ -202,6 +207,50 @@ describe("<PlantInstances />", () => {
202207
expect(mockNavigate).not.toHaveBeenCalled();
203208
});
204209

210+
const iconRaycast = (p = fakeProps()) => {
211+
const wrapper = createRenderer(<PlantInstances {...p} />);
212+
const mesh = wrapper.root.findAll(node =>
213+
(node.type as string) == "instancedMesh")[0];
214+
const raycast = mesh.props.raycast as (
215+
this: ThreeInstancedMesh,
216+
raycaster: Raycaster,
217+
intersects: Intersection[],
218+
) => void;
219+
unmountRenderer(wrapper);
220+
return raycast;
221+
};
222+
223+
it.each([
224+
Mode.clickToAdd,
225+
Mode.createPoint,
226+
Mode.createWeed,
227+
])("allows %s raycasts through plant icons", mode => {
228+
getModeSpy.mockReturnValue(mode);
229+
const defaultRaycast = jest.spyOn(
230+
ThreeInstancedMesh.prototype,
231+
"raycast",
232+
);
233+
const intersects: Intersection[] = [];
234+
const raycaster = {} as Raycaster;
235+
iconRaycast().call({} as ThreeInstancedMesh, raycaster, intersects);
236+
expect(defaultRaycast).not.toHaveBeenCalled();
237+
expect(intersects).toEqual([]);
238+
defaultRaycast.mockRestore();
239+
});
240+
241+
it("keeps plant icon raycasts outside placement modes", () => {
242+
getModeSpy.mockReturnValue(Mode.none);
243+
const defaultRaycast = jest.spyOn(
244+
ThreeInstancedMesh.prototype,
245+
"raycast",
246+
).mockImplementation(() => undefined);
247+
const intersects: Intersection[] = [];
248+
const raycaster = {} as Raycaster;
249+
iconRaycast().call({} as ThreeInstancedMesh, raycaster, intersects);
250+
expect(defaultRaycast).toHaveBeenCalledWith(raycaster, intersects);
251+
defaultRaycast.mockRestore();
252+
});
253+
205254
it("doesn't navigate with missing instanceId", () => {
206255
setMockInstanceId(undefined);
207256
const p = fakeProps();

frontend/three_d_garden/garden/plant_instances.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import React from "react";
22
import {
3-
InstancedMesh as InstancedMeshType,
3+
InstancedMesh as ThreeInstancedMesh,
44
Matrix4,
55
Quaternion,
66
Vector3,
77
MeshBasicMaterial as ThreeMeshBasicMaterial,
8+
type Intersection,
9+
type Raycaster,
810
} from "three";
911
import { ThreeEvent, useFrame } from "@react-three/fiber";
1012
import { useNavigate } from "react-router";
@@ -60,6 +62,15 @@ const newPlantIconUpdateState = (): PlantIconUpdateState => ({
6062
export const plantIconBrightness = (sunFactor?: number) =>
6163
Math.max(0.25, sunFactor ?? 1);
6264

65+
const plantIconRaycast = function (
66+
this: ThreeInstancedMesh,
67+
raycaster: Raycaster,
68+
intersects: Intersection[],
69+
) {
70+
if (HOVER_OBJECT_MODES.includes(getMode())) { return; }
71+
ThreeInstancedMesh.prototype.raycast.call(this, raycaster, intersects);
72+
};
73+
6374
const PlantIconInstances = (props: PlantIconInstancesProps) => {
6475
const {
6576
config, plants, icon, visible, startTimeRef, dispatch, getZ, plantIndexes,
@@ -71,7 +82,7 @@ const PlantIconInstances = (props: PlantIconInstancesProps) => {
7182
() => getPlantIconTexture(baseTexture, icon),
7283
[baseTexture, icon]);
7384
// eslint-disable-next-line no-null/no-null
74-
const instancedRef = React.useRef<InstancedMeshType>(null);
85+
const instancedRef = React.useRef<ThreeInstancedMesh>(null);
7586
// eslint-disable-next-line no-null/no-null
7687
const materialRef = React.useRef<ThreeMeshBasicMaterial>(null);
7788
const lastBrightness = React.useRef<number | undefined>(undefined);
@@ -168,6 +179,7 @@ const PlantIconInstances = (props: PlantIconInstancesProps) => {
168179
frustumCulled={false}
169180
userData={{ plantIndexes }}
170181
visible={visible}
182+
raycast={plantIconRaycast}
171183
onClick={onClick}
172184
renderOrder={RenderOrder.plants}>
173185
<PlaneGeometry args={[1, 1]} />

0 commit comments

Comments
 (0)