Tag
Previous chapter recalls: points in three dimensions
you understand the Live SDK event system and try to develop a small app to get a point of three-dimensional location by clicking on the event.
Set labels in three-dimensional space.
Preparatory work
We create a new directory (src/5.tagging
and corresponding htm files and jsx or tsx files. The State code with the previous chapter is too onerous, and we show the base development of the contents of the three-dimensional space chapter from.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="data:;base64,iVBORw0KGgo=" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Tag | Tagging</title>
<style>
* {
margin: 0;
padding: 0;
}
html,
body #app {
width: 100%;
height: 100%;
overflow: hidden;
}
</style>
</head>
<body>
<div id="app"></div>
<script type="module" src="./index"></script>
</body>
</html>
- JavaScript
- TypeScript
import { useState, useEffect } from "react";
import { parseWork } from "@realsee/five";
/**
* React Hook: via work. json address get work object
* @param url work.json data address
* @returns work object, if getting, return null
*/
function useFetchWork(url) {
const [work, setWork] = useState(null);
useEffect(() => {
setWork(null);
fetch(url)
.then((response) => response.text())
.then((text) => setWork(parseWork(text)));
}, [url]);
return work;
}
export { useFetchWork };
import { useState, useEffect } from "react";
/**
* Get the size of the current window
*/
function getWindowDimensions() {
return { width: window.innerWidth, height: window.innerHeight };
}
/**
* React Hook: Get the size of the current window
*/
function useWindowDimensions() {
const [size, setSize] = useState(getWindowDimensions);
useEffect(() => {
const listener = () => setSize(getWindowDimensions());
window.addEventListener("resize", listener, false);
return () => window.removeEventListener("resize", listener, false);
});
return size;
}
export { useWindowDimensions };
import React from "react";
import { Five } from "@realsee/five";
import { useFiveCurrentState } from "@realsee/five/react";
import BottomNavigation from "@mui/material/BottomNavigation";
import BottomNavigationAction from "@mui/material/BottomNavigationAction";
import Paper from "@mui/material/Paper";
import DirectionsWalkIcon from "@mui/icons-material/DirectionsWalk";
import ViewInArIcon from "@mui/icons-material/ViewInAr";
/**
* React Component: Modal Control
*/
const ModeController = () => {
const [state, setState] = useFiveCurrentState();
return (
<Paper sx={{ position: "fixed", bottom: 0, left: 0, right: 0 }}>
<BottomNavigation
showLabels
value={state.mode}
onChange={(_, newValue) => {
setState({ mode: newValue });
}}
>
<BottomNavigationAction
label="Panorama roaming"
icon={<DirectionsWalkIcon />}
value={Five.Mode.Panorama}
/>
<BottomNavigationAction
label="Space overview"
icon={<ViewInArIcon />}
value={Five.Mode.Floorplan}
/>
</BottomNavigation>
</Paper>
);
};
export { ModeController };
import React from "react";
import { createFiveProvider, FiveCanvas } from "@realsee/five/react";
import { useFetchWork } from "./useFetchWork";
import { useWindowDimensions } from "./useWindowDimensions";
import { ModeController } from "./ModeController";
/** work. The data URL */
const workURL =
"https://vrlab-public.ljcdn.com/release/static/image/release/five/work-sample/07bdc58f413bc5494f05c7cbb5cbdce4/work.json";
const FiveProvider = createFiveProvider();
const App = () => {
const work = useFetchWork(workURL);
const size = useWindowDimensions();
return (
work && (
<FiveProvider initialWork={work}>
<FiveCanvas {...size} />
<ModeController />
</FiveProvider>
)
);
};
export { App };
import React from "react";
import ReactDOM from "react-dom";
import { App } from "./App";
ReactDOM.render(<App />, document.querySelector("#app"));
export {};
import { useState, useEffect } from "react";
import { Work, parseWork } from "@realsee/five";
/**
* React Hook: through work. Son addresses get work object
* @param url work. Son data address
* @returns work object if fetching, Recall null
*/
function useFetchWork(url: string) {
const [work, setWork] = useState<Work | null>(null);
useEffect(() => {
setWork(null);
fetch(url)
.then((response) => response.text())
.then((text) => setWork(parseWork(text)));
}, [url]);
return work;
}
export { useFetchWork };
import { useState, useEffect } from "react";
/**
* - Get the size of the current window
*/
function getWindowDimensions() {
return { width: window.innerWidth, height: window.innerHeight };
}
/**
* - React Hook: Get the size of the current window
*/
function useWindowDimensions() {
const [size, setSize] = useState(getWindowDimensions);
useEffect(() => {
const listener = () => setSize(getWindowDimensions());
window.addEventListener("resize", listener, false);
return () => window.removeEventListener("resize", listener, false);
});
return size;
}
export { useWindowDimensions };
import React, { FC } from "react";
import { Five, Mode } from "@realsee/five";
import { useFiveCurrentState } from "@realsee/five/react";
import BottomNavigation from "@mui/material/BottomNavigation";
import BottomNavigationAction from "@mui/material/BottomNavigationAction";
import Paper from "@mui/material/Paper";
import DirectionsWalkIcon from "@mui/icons-material/DirectionsWalk";
import ViewInArIcon from "@mui/icons-material/ViewInAr";
/**
* React Component : ModeController: Mode Controler
*/
const ModeController: FC = () => {
const [state, setState] = useFiveCurrentState();
return (
<Paper sx={{ position: "fixed", bottom: 0, left: 0, right: 0 }}>
<BottomNavigation
showLabels
value={state.mode}
onChange={(_, newValue: Mode) => {
setState({ mode: newValue });
}}
>
<BottomNavigationAction
label="Panorama roaming"
icon={<DirectionsWalkIcon />}
value={Five.Mode.Panorama}
/>
<BottomNavigationAction
label="Space overview"
icon={<ViewInArIcon />}
value={Five.Mode.Floorplan}
/>
</BottomNavigation>
</Paper>
);
};
export { ModeController };
import React, { FC } from "react";
import { createFiveProvider, FiveCanvas } from "@realsee/five/react";
import { useFetchWork } from "./useFetchWork";
import { useWindowDimensions } from "./useWindowDimensions";
import { ModeController } from "./ModeController";
/** data URL of work.json */
const workURL =
"https://vrlab-public.ljcdn.com/release/static/image/release/five/work-sample/07bdc58f413bc5494f05c7cbb5cbdce4/work.json";
const FiveProvider = createFiveProvider();
const App: FC = () => {
const work = useFetchWork(workURL);
const size = useWindowDimensions();
return (
work && (
<FiveProvider initialWork={work}>
<FiveCanvas {...size} />
<ModeController />
</FiveProvider>
)
);
};
export { App };
import React from "react";
import ReactDOM from "react-dom";
import { App } from "./App";
ReactDOM.render(<App />, document.querySelector("#app"));
export {};
Start service npm run dev
and jump to the current page "http://localhost:3000/src/5.tagging/index.html".
Please see your console. The port number will change due to your configuration and current port occupancy, please check the console output. If you use another development build tool, please start the service as required by your own development build tool.
Developing Tag Features
Add Tag Style
Add a tag style to the html.
Style is not necessary, it is only for better labels.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="data:;base64,iVBORw0KGgo=" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Tag | Tagging</title>
<style>
* {
margin: 0;
padding: 0;
}
html,
body #app {
width: 100%;
height: 100%;
overflow: hidden;
}
.tag {
position: absolute;
width: 0;
height: 0;
transform: translateZ(0);
}
.tag-pannel {
position: absolute;
width: 100px;
min-height: 20px;
transform: translate(-50%, 0);
left: 50%;
bottom: 10px;
background: #333;
color: #fff;
border-radius: 2px;
text-align: center;
line-height: 20px;
padding: 8px;
font-size: 14px;
}
.tag-pannel:after {
content: "";
display: block;
position: absolute;
width: 10px;
height: 10px;
left: 50%;
bottom: -5px;
transform: translate(-50%, 0) rotate(45deg);
background: #333;
pointer-events: none;
}
</style>
</head>
<body>
<div id="app"></div>
<script type="module" src="./index"></script>
</body>
</html>
useFiveProject2d Description
This chapter will use the useFiveProject2d
method.He can match the three-dimensional coordinates to the 2D screen.
useFiveProject2d (vector: THREE.Vector3, testModel: boolean): THREE.Vector2 | null
- Incoming a three-dimensional coordinates to get a 2D coordinates of the screen, starting at the top of the left in pixels.Can be used as
{ left: returnValue.x + "px", top: returnValue.y + "px" }
etc. - If three-dimensional coordinates cannot be calculated in the screen (e.g. behind or blocked), then return
null
. - The second parameter testModel is calculated if the model collided, i.e. if the coordinate blocked by the model is
null
.
Write TaggingController
- Add a TaggingController file to write components.
- Store labels and text with
tags
React state - Store newly created tags with
newTag
React state - Listen to the
intersectionOnModelUpdate
event to place the newly created tab in the mouse position. - Call useProject2 with a tag
project2
method (intagElement
) to get the screen canvas coordinates and render them by changing styles.
- JavaScript
- TypeScript
import React, { useState, useCallback } from "react";
import { useFiveEventCallback, useFiveProject2d } from "@realsee/five/react";
import Button from "@mui/material/Button";
import Paper from "@mui/material/Paper";
/**
* React Component: Tag
*/
const TaggingController = () => {
const project2d = useFiveProject2d();
const [tags, setTags] = useState([]);
const [newTag, setNewTag] = useState(null);
const tagElement = useCallback((tag, key) => {
const position = tag.position && project2d(tag.position, true);
const style = position
? { left: position.x, top: position.y }
: { display: "none" };
return (
<div className="tag" style={style} key={key}>
<div className="tag-pannel">
<span className="tag-content">{tag.label}</span>
</div>
</div>
);
}, []);
const addTag = useCallback(() => {
setNewTag({ label: window.prompt("Add Tag", "") || "Untitled" });
}, []) ;
useFiveEventCallback(
"intersectionOnModelUpdate",
(intersect) => {
if (newTag) setNewTag({ position: intersect.point, label: newTag.label });
},
[newTag]
);
useFiveEventCallback(
"wantsTapGesture",
(raycaster) => {
if (newTag && newTag.position) {
setTags((tags) => tags.concat(newTag));
setNewTag(null);
return false;
}
},
[newTag]
);
return (
<React.Fragment>
<Paper sx={{ position: "fixed", top: 10, left: 10 }}>
<Button onClick={addTag}>Tag</Button>
</Paper>
{newTag && tagElement(newTag)}
{tags.map((tag, index) => tagElement(tag, index))}
</React.Fragment>
);
};
export { TaggingController };
import * as THREE from "three";
import React, { FC, useState, useCallback } from "react";
import { useFiveEventCallback, useFiveProject2d } from "@realsee/five/react";
import Button from "@mui/material/Button";
import Paper from "@mui/material/Paper";
/**
* React Component: Tag
*/
const TaggingController: FC = () => {
type Tag = { position?: THREE.Vector3; label: string };
const project2d = useFiveProject2d();
const [tags, setTags] = useState<Tag[]>([]);
const [newTag, setNewTag] = useState<Tag | null>(null);
const tagElement = useCallback((tag, key?: number | string) => {
const position = tag.position && project2d(tag.position, true);
const style = position
? { left: position.x, top: position.y }
: { display: "none" };
return (
<div className="tag" style={style} key={key}>
<div className="tag-pannel">
<span className="tag-content">{tag.label}</span>
</div>
</div>
);
}, []);
const addTag = useCallback(() => {
setNewTag({ label: window.prompt("Add Tag", "") || "Untitled" });
}, []);
useFiveEventCallback(
"intersectionOnModelUpdate",
(intersect) => {
if (newTag) setNewTag({ position: intersect.point, label: newTag.label });
},
[newTag]
);
useFiveEventCallback(
"wantsTapGesture",
(raycaster) => {
if (newTag && newTag.position) {
setTags((tags) => tags.concat(newTag));
setNewTag(null);
return false;
}
},
[newTag]
);
return (
<React.Fragment>
<Paper sx={{ position: "fixed", top: 10, left: 10 }}>
<Button onClick={addTag}>Tag</Button>
</Paper>
{newTag && tagElement(newTag)}
{tags.map((tag, index) => tagElement(tag, index))}
</React.Fragment>
);
};
export { TaggingController };
Use Tag Component
Insert into App file GiveProvider
- JavaScript
- TypeScript
import React from "react";
import { createFiveProvider, FiveCanvas } from "@realsee/five/react";
import { useFetchWork } from "./useFetchWork";
import { useWindowDimensions } from "./useWindowDimensions";
import { ModeController } from "./ModeController";
import { TaggingController } from "./TaggingController";
/** work. The data URL */
const workURL =
"https://vrlab-public.ljcdn.com/release/static/image/release/five/work-sample/07bdc58f413bc5494f05c7cbb5cbdce4/work.json";
const FiveProvider = createFiveProvider();
const App = () => {
const work = useFetchWork(workURL);
const size = useWindowDimensions();
return (
work && (
<FiveProvider initialWork={work}>
<FiveCanvas {...size} />
<ModeController />
<TaggingController />
</FiveProvider>
)
);
};
export { App };
import React, { FC } from "react";
import { createFiveProvider, FiveCanvas } from "@realsee/five/react";
import { useFetchWork } from "./useFetchWork";
import { useWindowDimensions } from "./useWindowDimensions";
import { ModeController } from "./ModeController";
import { TaggingController } from "./TaggingController";
/*_ work. The data URL _*/
const workURL =
"https://vrlab-public.ljcdn.com/release/static/image/release/five/work-sample/07bdc58f413bc5494f05c7cbb5cbdce4/work.json";
const FiveProvider = createFiveProvider();
const App: FC = () => {
const work = useFetchWork(workURL);
const size = useWindowDimensions();
return (
work && (
<FiveProvider initialWork={work}>
<FiveCanvas {...size} />
<ModeController />
<TaggingController />
</FiveProvider>
)
);
};
export { App };
Returning to your browser for viewing will find tabs in the upper left corner of your page, click on, fill in the tag name, move the mouse, click in the position you need and place the tag.
Well, it is a functional feature: partying_face: