You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
415 lines
22 KiB
415 lines
22 KiB
<!DOCTYPE html><html lang="ko"><head>
|
|
<meta charset="utf-8">
|
|
<title>VR - Look to Select</title>
|
|
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
|
|
<meta name="twitter:card" content="summary_large_image">
|
|
<meta name="twitter:site" content="@threejs">
|
|
<meta name="twitter:title" content="Three.js – VR - Look to Select">
|
|
<meta property="og:image" content="https://threejs.org/files/share.png">
|
|
<link rel="shortcut icon" href="/files/favicon_white.ico" media="(prefers-color-scheme: dark)">
|
|
<link rel="shortcut icon" href="/files/favicon.ico" media="(prefers-color-scheme: light)">
|
|
|
|
<link rel="stylesheet" href="/manual/resources/lesson.css">
|
|
<link rel="stylesheet" href="/manual/resources/lang.css">
|
|
<!-- Import maps polyfill -->
|
|
<!-- Remove this when import maps will be widely supported -->
|
|
<script async src="https://unpkg.com/es-module-shims@1.3.6/dist/es-module-shims.js"></script>
|
|
|
|
<script type="importmap">
|
|
{
|
|
"imports": {
|
|
"three": "../../build/three.module.js"
|
|
}
|
|
}
|
|
</script>
|
|
<link rel="stylesheet" href="/manual/ko/lang.css">
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<div class="lesson-title">
|
|
<h1>VR - Look to Select</h1>
|
|
</div>
|
|
<div class="lesson">
|
|
<div class="lesson-main">
|
|
<p><strong>NOTE: 이 페이지의 예시에는 VR 지원 기기가 필요합니다.
|
|
VR 기기 없이는 동작하지 않으며 그 이유를 <a href="webxr.html">이전 글</a>
|
|
에서 확인할 수 있습니다.</strong></p>
|
|
<p><a href="webxr.html">이전 글</a>에서 우리는 three.js를 사용한 매우 간단한 VR 예제를 살펴보고 다양한 종류의 VR 시스템에 대해 이야기했습니다.</p>
|
|
<p>가장 간단하고 흔한 것은 기본적으로 5달러에서 50달러의 얼굴 마스크에 넣는 전화기인 VR 구글 카드 보드 스타일입니다.
|
|
이런 종류의 VR에는 컨트롤러가 없기 때문에 사람들은 사용자 입력을 허용하기 위한 창의적인 해결책을 생각해 내야 합니다.</p>
|
|
<p>이때 가장 일반적인 해결책은 사용자가 무언가를 잠시 동안 가리킬 경우 그것이 선택되는 "Look to Select"입니다.</p>
|
|
<p>"Look to Select"를 구현해봅시다! 먼저 <a href="webxr.html">이전 글의 예시</a>에서 시작해 <a href="picking.html">Three.js 피킹</a>에서 만든 <code class="notranslate" translate="no">PickHelper</code>를 추가할 것입니다.</p>
|
|
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">class PickHelper {
|
|
constructor() {
|
|
this.raycaster = new THREE.Raycaster();
|
|
this.pickedObject = null;
|
|
this.pickedObjectSavedColor = 0;
|
|
}
|
|
pick(normalizedPosition, scene, camera, time) {
|
|
// restore the color if there is a picked object
|
|
if (this.pickedObject) {
|
|
this.pickedObject.material.emissive.setHex(this.pickedObjectSavedColor);
|
|
this.pickedObject = undefined;
|
|
}
|
|
|
|
// cast a ray through the frustum
|
|
this.raycaster.setFromCamera(normalizedPosition, camera);
|
|
// get the list of objects the ray intersected
|
|
const intersectedObjects = this.raycaster.intersectObjects(scene.children);
|
|
if (intersectedObjects.length) {
|
|
// pick the first object. It's the closest one
|
|
this.pickedObject = intersectedObjects[0].object;
|
|
// save its color
|
|
this.pickedObjectSavedColor = this.pickedObject.material.emissive.getHex();
|
|
// set its emissive color to flashing red/yellow
|
|
this.pickedObject.material.emissive.setHex((time * 8) % 2 > 1 ? 0xFFFF00 : 0xFF0000);
|
|
}
|
|
}
|
|
}
|
|
</pre>
|
|
<p>해당 코드에 대한 설명은 <a href="picking.html">피킹에 대한 글</a>을 참조하십시오.</p>
|
|
<p>이 기능을 사용하려면 인스턴스를 만들고 render loop에서 호출하기만 하면 됩니다.</p>
|
|
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">+const pickHelper = new PickHelper();
|
|
|
|
...
|
|
function render(time) {
|
|
time *= 0.001;
|
|
|
|
...
|
|
|
|
+ // 0, 0 is the center of the view in normalized coordinates.
|
|
+ pickHelper.pick({x: 0, y: 0}, scene, camera, time);
|
|
</pre>
|
|
<p>원래의 피킹 예시에서 우리는 마우스 좌표를 CSS 픽셀에서 캔버스를 가로질러 -1에서 +1로 가는 정규화된 좌표로 변환했습니다.</p>
|
|
<p>이 경우 우리는 항상 카메라가 마주 보고 있는 화면의 중심을 선택하기 때문에 정규화된 좌표의 중심인 x와 y 모두에 대해 0을 통과합니다.</p>
|
|
<p>그리고 우리가 그 물체들을 볼 때 그 물체들은 번쩍거릴 것입니다.</p>
|
|
<p></p><div translate="no" class="threejs_example_container notranslate">
|
|
<div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/threejs-webvr-look-to-select.html"></iframe></div>
|
|
<a class="threejs_center" href="/manual/examples/threejs-webvr-look-to-select.html" target="_blank">새 탭에서 보기</a>
|
|
</div>
|
|
|
|
<p></p>
|
|
<p>일반적으로 우리는 즉각적인 선택을 원하지 않습니다.</p>
|
|
<p>대신 우리는 실수로 어떤 것을 선택하지 않도록 하기 위해 몇 분 동안 카메라를 사용자가 선택하고자 하는 것에 고정시키도록 합니다.</p>
|
|
<p>그러기 위해서 사용자가 계속 보고 있었는지, 그리고 얼마나 오래 있었는지를 전달하기 위한 일종의 미터나 게이지나 방법이 필요합니다.</p>
|
|
<p>이를 위한 한 가지 쉬운 방법은 2가지 색상의 텍스처를 만들고 텍스처 오프셋을 사용하여 모델을 가로질러 텍스처를 이동시키는 것입니다.</p>
|
|
<p>VR 예제에 추가하기 전에 스스로 작동하는지 보도록 합시다.</p>
|
|
<p>먼저 <a href="/docs/#api/ko/cameras/OrthographicCamera"><code class="notranslate" translate="no">OrthographicCamera</code></a>를 만들어 보겠습니다.</p>
|
|
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const left = -2; // Use values for left
|
|
const right = 2; // right, top and bottom
|
|
const top = 1; // that match the default
|
|
const bottom = -1; // canvas size.
|
|
const near = -1;
|
|
const far = 1;
|
|
const camera = new THREE.OrthographicCamera(left, right, top, bottom, near, far);
|
|
</pre>
|
|
<p>그리고 캔버스의 크기가 변경되면 업데이트하는 것을 잊지 마십시오.</p>
|
|
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function render(time) {
|
|
time *= 0.001;
|
|
|
|
if (resizeRendererToDisplaySize(renderer)) {
|
|
const canvas = renderer.domElement;
|
|
const aspect = canvas.clientWidth / canvas.clientHeight;
|
|
+ camera.left = -aspect;
|
|
+ camera.right = aspect;
|
|
camera.updateProjectionMatrix();
|
|
}
|
|
...
|
|
</pre>
|
|
<p>우리는 현재 중앙 위아래 두 유닛과 좌우 측면 유닛을 보여주는 카메라를 가지고 있습니다.</p>
|
|
<p>다음으로 2가지 색 텍스처를 만들어 봅시다. 몇 군데 <a href="indexed-textures.html">다른</a> <a href="post-processing-3dlut.html">곳에서</a> 사용했던 <a href="/docs/#api/ko/textures/DataTexture"><code class="notranslate" translate="no">DataTexture</code></a>를 사용할 것입니다.</p>
|
|
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function makeDataTexture(data, width, height) {
|
|
const texture = new THREE.DataTexture(data, width, height, THREE.RGBAFormat);
|
|
texture.minFilter = THREE.NearestFilter;
|
|
texture.magFilter = THREE.NearestFilter;
|
|
texture.needsUpdate = true;
|
|
return texture;
|
|
}
|
|
|
|
const cursorColors = new Uint8Array([
|
|
64, 64, 64, 64, // dark gray
|
|
255, 255, 255, 255, // white
|
|
]);
|
|
const cursorTexture = makeDataTexture(cursorColors, 2, 1);
|
|
</pre>
|
|
<p>그 다음 <a href="/docs/#api/ko/geometries/TorusGeometry"><code class="notranslate" translate="no">TorusGeometry</code></a>에 있는 텍스처를 사용할 것입니다.</p>
|
|
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const ringRadius = 0.4;
|
|
const tubeRadius = 0.1;
|
|
const tubeSegments = 4;
|
|
const ringSegments = 64;
|
|
const cursorGeometry = new THREE.TorusGeometry(
|
|
ringRadius, tubeRadius, tubeSegments, ringSegments);
|
|
|
|
const cursorMaterial = new THREE.MeshBasicMaterial({
|
|
color: 'white',
|
|
map: cursorTexture,
|
|
transparent: true,
|
|
blending: THREE.CustomBlending,
|
|
blendSrc: THREE.OneMinusDstColorFactor,
|
|
blendDst: THREE.OneMinusSrcColorFactor,
|
|
});
|
|
const cursor = new THREE.Mesh(cursorGeometry, cursorMaterial);
|
|
scene.add(cursor);
|
|
</pre>
|
|
<p>그 다음 <code class="notranslate" translate="no">render</code>에서 텍스처의 오프셋을 조정하도록 합니다.</p>
|
|
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">function render(time) {
|
|
time *= 0.001;
|
|
|
|
if (resizeRendererToDisplaySize(renderer)) {
|
|
const canvas = renderer.domElement;
|
|
const aspect = canvas.clientWidth / canvas.clientHeight;
|
|
camera.left = -aspect;
|
|
camera.right = aspect;
|
|
camera.updateProjectionMatrix();
|
|
}
|
|
|
|
+ const fromStart = 0;
|
|
+ const fromEnd = 2;
|
|
+ const toStart = -0.5;
|
|
+ const toEnd = 0.5;
|
|
+ cursorTexture.offset.x = THREE.MathUtils.mapLinear(
|
|
+ time % 2,
|
|
+ fromStart, fromEnd,
|
|
+ toStart, toEnd);
|
|
|
|
renderer.render(scene, camera);
|
|
}
|
|
</pre>
|
|
<p><code class="notranslate" translate="no">THREE.MathUtils.mapLinear</code>는 <code class="notranslate" translate="no">fromStart</code>와 <code class="notranslate" translate="no">fromEnd</code> 사이의 값을 취하여 시작과 끝 사이의 값으로 매핑합니다.</p>
|
|
<p>위의 경우, 0에서 2까지의 값을 의미하는 <code class="notranslate" translate="no">time % 2</code>를 취하여 -0.5에서 0.5까지의 값에 매핑합니다.</p>
|
|
<p><a href="textures.html">텍스처</a>는 0에서 1까지 정규화된 텍스처 좌표를 사용하여 geometry에 매핑됩니다.
|
|
즉, 기본 래핑 모드인 <code class="notranslate" translate="no">THREE.ClampToEdge</code>로 설정된 2x1 픽셀 이미지를 의미하며,
|
|
텍스처 좌표를 -0.5만큼 조정하면 전체 메시가 첫 번째 색상이 되고 텍스처 좌표를 +0.5만큼 조정하면 전체 메시가 두 번째 색상이 됩니다.
|
|
필터링을 <code class="notranslate" translate="no">THREE.NearestFilter</code>로 설정하면 geometry를 통해 두 색상 간의 전환이 가능해집니다.</p>
|
|
<p><a href="backgrounds.html">배경과 관련된 글</a>에서 다루었던 것처럼 배경의 질감을 더해봅시다.
|
|
2x2 색상 셋을 사용하지만 텍스처의 반복 설정을 8x8 그리드로 설정할 수 있습니다.
|
|
이렇게 하면 커서가 렌더링 되어 다른 색상과 대조하여 확인할 수 있습니다.</p>
|
|
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">+const backgroundColors = new Uint8Array([
|
|
+ 0, 0, 0, 255, // black
|
|
+ 90, 38, 38, 255, // dark red
|
|
+ 100, 175, 103, 255, // medium green
|
|
+ 255, 239, 151, 255, // light yellow
|
|
+]);
|
|
+const backgroundTexture = makeDataTexture(backgroundColors, 2, 2);
|
|
+backgroundTexture.wrapS = THREE.RepeatWrapping;
|
|
+backgroundTexture.wrapT = THREE.RepeatWrapping;
|
|
+backgroundTexture.repeat.set(4, 4);
|
|
|
|
const scene = new THREE.Scene();
|
|
+scene.background = backgroundTexture;
|
|
</pre>
|
|
<p>이제 이것을 실행하면 게이지와 같은 원을 얻을 수 있고 게이지 위치를 설정할 수 있습니다.</p>
|
|
<p></p><div translate="no" class="threejs_example_container notranslate">
|
|
<div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/threejs-webvr-look-to-select-selector.html"></iframe></div>
|
|
<a class="threejs_center" href="/manual/examples/threejs-webvr-look-to-select-selector.html" target="_blank">새 탭에서 보기</a>
|
|
</div>
|
|
|
|
<p></p>
|
|
<p>몇 가지 주목하고 <strong>시도해야 할 것들</strong>이 있습니다.</p>
|
|
<ul>
|
|
<li><p>다음과 같이 <code class="notranslate" translate="no">cursorMaterial</code>의 <code class="notranslate" translate="no">blending</code>, <code class="notranslate" translate="no">blendSrc</code>, <code class="notranslate" translate="no">blendDst</code>
|
|
속성을 설정합니다.</p>
|
|
<pre class="prettyprint showlinemods notranslate notranslate" translate="no"> blending: THREE.CustomBlending,
|
|
blendSrc: THREE.OneMinusDstColorFactor,
|
|
blendDst: THREE.OneMinusSrcColorFactor,
|
|
</pre><p>이것은 효과의 <em>역</em> 타입으로 주어집니다.
|
|
그 세 줄에 주석을 달면 차이를 알 수 있을 것입니다.
|
|
저는 역효과가 가장 좋다고 생각하는데, 이렇게 하면 커서의 색깔에 상관없이 커서가 보일 수 있기 때문입니다.</p>
|
|
</li>
|
|
<li><p><a href="/docs/#api/ko/geometries/RingGeometry"><code class="notranslate" translate="no">RingGeometry</code></a>가 아닌 <a href="/docs/#api/ko/geometries/TorusGeometry"><code class="notranslate" translate="no">TorusGeometry</code></a>를 사용해 봅시다.</p>
|
|
<p>어떤 이유로든 <a href="/docs/#api/ko/geometries/RingGeometry"><code class="notranslate" translate="no">RingGeometry</code></a>는 평평한 UV 매핑 방식을 사용합니다.
|
|
이 때문에 <a href="/docs/#api/ko/geometries/RingGeometry"><code class="notranslate" translate="no">RingGeometry</code></a>를 사용하면 위에서처럼 링 주위가 아닌 수평으로 링을 가로질러 텍스처가 미끄러집니다.</p>
|
|
<p>이걸 시도해 보고 <a href="/docs/#api/ko/geometries/TorusGeometry"><code class="notranslate" translate="no">TorusGeometry</code></a>를 <a href="/docs/#api/ko/geometries/RingGeometry"><code class="notranslate" translate="no">RingGeometry</code></a>(위 예시에서 설명한 대로)로 바꾸면 무슨 뜻인지 알 수 있을 것입니다.</p>
|
|
<p><em>적절한</em> 정의를 위한 <em>적절한</em> 할 것은 <a href="/docs/#api/ko/geometries/RingGeometry"><code class="notranslate" translate="no">RingGeometry</code></a>를 사용하되 링 주위를 돌도록 텍스처 좌표를 고정하는 것입니다.
|
|
아니면, 자신만의 링 지오메트리를 생성하세요. 그래도 torus는 잘 작동합니다.
|
|
<a href="/docs/#api/ko/materials/MeshBasicMaterial"><code class="notranslate" translate="no">MeshBasicMaterial</code></a>과 함께 카메라 바로 앞에 배치하면 링과 똑같이 보이고 텍스처 좌표가 링 주위를 돌기 때문에 우리가 원하는 대로 작동합니다.</p>
|
|
</li>
|
|
</ul>
|
|
<p>이제 이것을 위의 VR 코드와 통합해 봅시다.</p>
|
|
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">class PickHelper {
|
|
- constructor() {
|
|
+ constructor(camera) {
|
|
this.raycaster = new THREE.Raycaster();
|
|
this.pickedObject = null;
|
|
- this.pickedObjectSavedColor = 0;
|
|
|
|
+ const cursorColors = new Uint8Array([
|
|
+ 64, 64, 64, 64, // dark gray
|
|
+ 255, 255, 255, 255, // white
|
|
+ ]);
|
|
+ this.cursorTexture = makeDataTexture(cursorColors, 2, 1);
|
|
+
|
|
+ const ringRadius = 0.4;
|
|
+ const tubeRadius = 0.1;
|
|
+ const tubeSegments = 4;
|
|
+ const ringSegments = 64;
|
|
+ const cursorGeometry = new THREE.TorusGeometry(
|
|
+ ringRadius, tubeRadius, tubeSegments, ringSegments);
|
|
+
|
|
+ const cursorMaterial = new THREE.MeshBasicMaterial({
|
|
+ color: 'white',
|
|
+ map: this.cursorTexture,
|
|
+ transparent: true,
|
|
+ blending: THREE.CustomBlending,
|
|
+ blendSrc: THREE.OneMinusDstColorFactor,
|
|
+ blendDst: THREE.OneMinusSrcColorFactor,
|
|
+ });
|
|
+ const cursor = new THREE.Mesh(cursorGeometry, cursorMaterial);
|
|
+ // add the cursor as a child of the camera
|
|
+ camera.add(cursor);
|
|
+ // and move it in front of the camera
|
|
+ cursor.position.z = -1;
|
|
+ const scale = 0.05;
|
|
+ cursor.scale.set(scale, scale, scale);
|
|
+ this.cursor = cursor;
|
|
+
|
|
+ this.selectTimer = 0;
|
|
+ this.selectDuration = 2;
|
|
+ this.lastTime = 0;
|
|
}
|
|
pick(normalizedPosition, scene, camera, time) {
|
|
+ const elapsedTime = time - this.lastTime;
|
|
+ this.lastTime = time;
|
|
|
|
- // restore the color if there is a picked object
|
|
- if (this.pickedObject) {
|
|
- this.pickedObject.material.emissive.setHex(this.pickedObjectSavedColor);
|
|
- this.pickedObject = undefined;
|
|
- }
|
|
|
|
+ const lastPickedObject = this.pickedObject;
|
|
+ this.pickedObject = undefined;
|
|
|
|
// cast a ray through the frustum
|
|
this.raycaster.setFromCamera(normalizedPosition, camera);
|
|
// get the list of objects the ray intersected
|
|
const intersectedObjects = this.raycaster.intersectObjects(scene.children);
|
|
if (intersectedObjects.length) {
|
|
// pick the first object. It's the closest one
|
|
this.pickedObject = intersectedObjects[0].object;
|
|
- // save its color
|
|
- this.pickedObjectSavedColor = this.pickedObject.material.emissive.getHex();
|
|
- // set its emissive color to flashing red/yellow
|
|
- this.pickedObject.material.emissive.setHex((time * 8) % 2 > 1 ? 0xFFFF00 : 0xFF0000);
|
|
}
|
|
|
|
+ // show the cursor only if it's hitting something
|
|
+ this.cursor.visible = this.pickedObject ? true : false;
|
|
+
|
|
+ let selected = false;
|
|
+
|
|
+ // if we're looking at the same object as before
|
|
+ // increment time select timer
|
|
+ if (this.pickedObject && lastPickedObject === this.pickedObject) {
|
|
+ this.selectTimer += elapsedTime;
|
|
+ if (this.selectTimer >= this.selectDuration) {
|
|
+ this.selectTimer = 0;
|
|
+ selected = true;
|
|
+ }
|
|
+ } else {
|
|
+ this.selectTimer = 0;
|
|
+ }
|
|
+
|
|
+ // set cursor material to show the timer state
|
|
+ const fromStart = 0;
|
|
+ const fromEnd = this.selectDuration;
|
|
+ const toStart = -0.5;
|
|
+ const toEnd = 0.5;
|
|
+ this.cursorTexture.offset.x = THREE.MathUtils.mapLinear(
|
|
+ this.selectTimer,
|
|
+ fromStart, fromEnd,
|
|
+ toStart, toEnd);
|
|
+
|
|
+ return selected ? this.pickedObject : undefined;
|
|
}
|
|
}
|
|
</pre>
|
|
<p>위의 코드를 보시면 커서 형상, 텍스처, 매테리얼을 만들기 위해 모든 코드를 추가한 것을 볼 수 있습니다.
|
|
그리고 카메라의 자식으로 추가해서 항상 카메라 앞에 놓이게 합니다.
|
|
커서가 렌더링 되지 않을 경우 카메라를 scene에 추가해야 합니다.</p>
|
|
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">+scene.add(camera);
|
|
</pre>
|
|
<p>이 다음 이번에 피킹 할 것이 지난번과 같은지 확인합니다.
|
|
타이머에 경과 시간을 추가하고 타이머가 한계치에 도달하면 선택한 항목을 반환합니다.</p>
|
|
<p>이제 큐브들을 고르는데 그것을 사용해 봅시다.
|
|
간단한 예로 3개의 구도 추가하겠습니다.
|
|
큐브를 선택하여 큐브를 숨기고 해당 구의 숨기기를 취소합니다.</p>
|
|
<p>먼저 구면 geometry를 만들어보겠습니다.</p>
|
|
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">const boxWidth = 1;
|
|
const boxHeight = 1;
|
|
const boxDepth = 1;
|
|
-const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);
|
|
+const boxGeometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);
|
|
+
|
|
+const sphereRadius = 0.5;
|
|
+const sphereGeometry = new THREE.SphereGeometry(sphereRadius);
|
|
</pre>
|
|
<p>그리고 세 쌍의 박스와 구 <a href="/docs/#api/ko/objects/Mesh"><code class="notranslate" translate="no">Mesh</code></a>를 만들어 봅시다. 각 <a href="/docs/#api/ko/objects/Mesh"><code class="notranslate" translate="no">Mesh</code></a>를 파트너와 연결할 수 있도록 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map"><code class="notranslate" translate="no">맵</code></a>을 사용할 것입니다.</p>
|
|
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">-const cubes = [
|
|
- makeInstance(geometry, 0x44aa88, 0),
|
|
- makeInstance(geometry, 0x8844aa, -2),
|
|
- makeInstance(geometry, 0xaa8844, 2),
|
|
-];
|
|
+const meshToMeshMap = new Map();
|
|
+[
|
|
+ { x: 0, boxColor: 0x44aa88, sphereColor: 0xFF4444, },
|
|
+ { x: 2, boxColor: 0x8844aa, sphereColor: 0x44FF44, },
|
|
+ { x: -2, boxColor: 0xaa8844, sphereColor: 0x4444FF, },
|
|
+].forEach((info) => {
|
|
+ const {x, boxColor, sphereColor} = info;
|
|
+ const sphere = makeInstance(sphereGeometry, sphereColor, x);
|
|
+ const box = makeInstance(boxGeometry, boxColor, x);
|
|
+ // hide the sphere
|
|
+ sphere.visible = false;
|
|
+ // map the sphere to the box
|
|
+ meshToMeshMap.set(box, sphere);
|
|
+ // map the box to the sphere
|
|
+ meshToMeshMap.set(sphere, box);
|
|
+});
|
|
</pre>
|
|
<p>큐브를 회전하는 <code class="notranslate" translate="no">render</code>에서 <code class="notranslate" translate="no">cubes</code> 대신 <code class="notranslate" translate="no">meshToMeshMap</code>를 반복해야 합니다.</p>
|
|
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">-cubes.forEach((cube, ndx) => {
|
|
+let ndx = 0;
|
|
+for (const mesh of meshToMeshMap.keys()) {
|
|
const speed = 1 + ndx * .1;
|
|
const rot = time * speed;
|
|
- cube.rotation.x = rot;
|
|
- cube.rotation.y = rot;
|
|
-});
|
|
+ mesh.rotation.x = rot;
|
|
+ mesh.rotation.y = rot;
|
|
+ ++ndx;
|
|
+}
|
|
</pre>
|
|
<p>이제 새로운 <code class="notranslate" translate="no">PickHelper</code> 구현을 사용하여 개체 중 하나를 선택할 수 있습니다. 이 옵션을 선택하면 개체를 숨기고 그 파트너를 드러냅니다.</p>
|
|
<pre class="prettyprint showlinemods notranslate lang-js" translate="no">// 0, 0 is the center of the view in normalized coordinates.
|
|
-pickHelper.pick({x: 0, y: 0}, scene, camera, time);
|
|
+const selectedObject = pickHelper.pick({x: 0, y: 0}, scene, camera, time);
|
|
+if (selectedObject) {
|
|
+ selectedObject.visible = false;
|
|
+ const partnerObject = meshToMeshMap.get(selectedObject);
|
|
+ partnerObject.visible = true;
|
|
+}
|
|
</pre>
|
|
<p>그리고 이를 통해 우리는 꽤 괜찮은 <em>look to select</em>를 구현해야 합니다.</p>
|
|
<p></p><div translate="no" class="threejs_example_container notranslate">
|
|
<div><iframe class="threejs_example notranslate" translate="no" style=" " src="/manual/examples/resources/editor.html?url=/manual/examples/threejs-webvr-look-to-select-w-cursor.html"></iframe></div>
|
|
<a class="threejs_center" href="/manual/examples/threejs-webvr-look-to-select-w-cursor.html" target="_blank">새 탭에서 보기</a>
|
|
</div>
|
|
|
|
<p></p>
|
|
<p>이 예제가 구글 카드 보드 레벨 UX의 "look to select"를 구현하는 방법에 대한 아이디어를 주었기를 바랍니다.
|
|
텍스쳐 좌표 오프셋을 사용한 슬라이딩 텍스쳐도 일반적으로 유용한 기법입니다.</p>
|
|
<p>다음으로는 <a href="webxr-point-to-select.html">VR 컨트롤러가 있는 사용자가 사물을 가리키고 이동할 수 있는 방법을 알아보겠습니다.</a>.</p>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="/manual/resources/prettify.js"></script>
|
|
<script src="/manual/resources/lesson.js"></script>
|
|
|
|
|
|
|
|
|
|
</body></html>
|