Skip to content

Commit

Permalink
Add Thor + Serpent cameraViews sample, asset license, and attribution (
Browse files Browse the repository at this point in the history
…google#3018)

* add cameraViews sample, asset license, and attribution

* updated text information for cameraViews sample
  • Loading branch information
hybridherbst authored Dec 8, 2021
1 parent 7b66b63 commit a715f7f
Show file tree
Hide file tree
Showing 5 changed files with 289 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"Thor and the Midgard Serpent" (https://skfb.ly/69X8V) by Mr. The Rich is licensed under Creative Commons Attribution (http://creativecommons.org/licenses/by/4.0/).
Binary file not shown.
Binary file not shown.
4 changes: 4 additions & 0 deletions packages/modelviewer.dev/data/examples.json
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,10 @@
{
"htmlId": "dimensions",
"name": "Dimensions"
},
{
"htmlId": "cameraViews",
"name": "Camera Views"
}
]
},
Expand Down
284 changes: 284 additions & 0 deletions packages/modelviewer.dev/examples/annotations/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -312,11 +312,295 @@ <h4 id="intro"><span class="font-medium">Show Dimensions. </span></h4>
</div>
</div>

<div class="sample">
<div id="cameraViews" class="demo"></div>
<div class="content">
<div class="wrapper">
<h4 id="intro"><span class="font-medium">Camera Views. </span></h4>
<p>
Hotspots can contain additional data attributes and can be interactive. This example attaches click events to hotspots to move the camera.
</p>
<p>
Showcase by <a href="https://twitter.com/hybridherbst">hybridherbst</a> / <a href="https://prefrontalcortex.de">prefrontal cortex</a>.<br/>
</p>
<example-snippet stamp-to="cameraViews" highlight-as="html">
<template>

<model-viewer
id="hotspot-camera-view-demo"
src="../../assets/SketchfabModels/ThorAndTheMidgardSerpent.glb"
alt="Thor and the Midgard Serpent"
shadow-intensity="0"
exposure="1"
camera-controls
touch-action="none"
camera-orbit="-8.142746deg 68.967deg 0.6179899m"
camera-target="-0.003m 0.0722m 0.0391m"
min-field-of-view="45deg"
interpolation-decay="200"
min-camera-orbit="auto auto 5%"
touch-action="none"
poster="../../assets/SketchfabModels/ThorAndTheMidgardSerpent.webp"
interaction-prompt="auto"
autoplay
ar
ar-modes="webxr scene-viewer quick-look"
quick-look-browsers="safari chrome"
oncontextmenu="return false;"
>
<button class="view-button" slot="hotspot-0"
data-position="-0.0569m 0.0969m -0.1398m" data-normal="-0.5829775m 0.2863482m -0.7603565m"
data-orbit="-50.94862deg 84.56856deg 0.06545582m" data-target="-0.04384604m 0.07348397m -0.1213202m" data-fov="4.91deg">
The Fighters
</button>
<button class="view-button" slot="hotspot-1"
data-position="-0.1997m 0.11766m 0.0056m" data-normal="-0.4421014m 0.04410423m 0.8958802m"
data-orbit="3.711166deg 92.3035deg 0.04335197m" data-target="-0.1879433m 0.1157161m -0.01563221m" data-fov="3.25deg">
Hold Tight!
</button>
<button class="view-button" slot="hotspot-2"
data-position="0.0608m 0.0566m 0.0605m" data-normal="0.2040984m 0.7985359m -0.56629m"
data-orbit="42.72974deg 84.74043deg 0.07104211m" data-target="0.0757959m 0.04128428m 0.07109568m" data-fov="5.33deg">
The Encounter
</button>
<button class="view-button" slot="hotspot-3"
data-position="0.1989m 0.16711m -0.0749m" data-normal="0.7045857m 0.1997957m -0.6809117m"
data-orbit="-40.11996deg 88.17818deg 0.07090651m" data-target="0.2011831m 0.1398312m -0.07917573m" data-fov="5.32deg">
Catapult
</button>
<button class="view-button" slot="hotspot-4"
data-position="0.0677m 0.18906m -0.0158m" data-normal="-0.008245394m 0.6207898m 0.7839338m"
data-orbit="-118.8446deg 98.83521deg 0.02526586m" data-target="0.06528695m 0.1753406m -0.01964653m" data-fov="1.89deg">
Thunder and Lightning
</button>
<button class="view-button" slot="hotspot-5"
data-position="-0.1418m -0.041m 0.174m" data-normal="-0.4924125m 0.4698265m 0.7326617m"
data-orbit="-2.305313deg 110.1798deg 0.04504082m" data-target="-0.1151219m -0.04192762m 0.1523764m" data-fov="3.38deg">
Knock Knock
</button>
<button class="view-button" slot="hotspot-6"
data-position="0.08414419m 0.134m -0.215m" data-normal="0.03777227m 0.06876653m -0.9969176m"
data-orbit="-37.54149deg 82.16209deg 0.0468692m" data-target="0.08566038m 0.1249514m -0.1939646m" data-fov="3.52deg">
Lucky Shot
</button>
<button class="view-button" slot="hotspot-7"
data-position="0.14598m 0.03177m -0.05945886m" data-normal="-0.9392524m 0.2397608m -0.2456009m"
data-orbit="-142.3926deg 86.45934deg 0.06213665m" data-target="0.1519967m 0.01904771m -0.05945886m" data-fov="0.06deg">
Get Away!
</button>
<button class="view-button" slot="hotspot-8"
data-position="0.0094m 0.0894m -0.15103m" data-normal="-0.3878782m 0.4957891m -0.7770094m"
data-orbit="-118.6729deg 117.571deg 0.03905975m" data-target="0.007600758m 0.06771782m -0.1386167m" data-fov="2.93deg">
The Jump
</button>
<button class="view-button" slot="hotspot-9"
data-position="-0.0658m 0.1786m -0.0183m" data-normal="0.7857152m 0.4059967m 0.46671m"
data-orbit="53.28236deg 95.91318deg 0.1102844m" data-target="-0.07579391m 0.1393538m -0.00851791m" data-fov="8.27deg">
The Beast
</button>
<button class="view-button" slot="hotspot-10"
data-position="0.02610224m 0.01458751m -0.004978945m" data-normal="-0.602551m 0.7856147m -0.1405055m"
data-orbit="-78.89725deg 77.17752deg 0.08451112m" data-target="0.02610223m 0.0145875m -0.004978945m" data-fov="6.34deg">
Treasure
</button>
<button class="view-button" slot="hotspot-11"
data-position="-0.1053838m 0.01610652m 0.1076345m" data-normal="-0.624763m 0.5176854m 0.5845283m"
data-orbit="10.89188deg 119.9775deg 0.03543022m" data-target="-0.1053838m 0.01610652m 0.1076345m" data-fov="2.66deg">
Desperation
</button>
</model-viewer>

<script type="module">
const modelViewer = document.querySelector("#hotspot-camera-view-demo");
const annotationClicked = (annotation) => {
let dataset = annotation.dataset;
modelViewer.cameraTarget = dataset.target;
modelViewer.cameraOrbit = dataset.orbit;
if(dataset.fov !== undefined)
modelViewer.fieldOfView = dataset.fov;
}

modelViewer.querySelectorAll('button').forEach((hotspot) => {
console.log("hello hotspot", hotspot);
hotspot.addEventListener('click', () => annotationClicked(hotspot));
});
</script>

<style type="text/css">
.view-button {
background: #fff;
border-radius: 4px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.25);
color: rgba(0, 0, 0, 0.8);
display: block;
font-family: Futura, Helvetica Neue, sans-serif;
font-size: 12px;
font-weight: 700;
max-width: 128px;
overflow-wrap: break-word;
padding: 0.5em 1em;
position: absolute;
width: max-content;
height: max-content;
transform: translate3d(-50%, -50%, 0);
}
</style>

<!-- nearly identical to the "Staging & Cameras/Panning" example -->
<script type="module">
const modelViewer = document.querySelector("#hotspot-camera-view-demo");
const tapDistance = 2;
let panning = false;
let panX, panY;
let startX, startY;
let lastX, lastY;
let metersPerPixel;

const startPan = () => {
const orbit = modelViewer.getCameraOrbit();
const {theta, phi, radius} = orbit;
const psi = theta - modelViewer.turntableRotation;
metersPerPixel = 0.75 * radius / modelViewer.getBoundingClientRect().height;
panX = [-Math.cos(psi), 0, Math.sin(psi)];
panY = [
-Math.cos(phi) * Math.sin(psi),
Math.sin(phi),
-Math.cos(phi) * Math.cos(psi)
];
modelViewer.interactionPrompt = 'none';
};

const stopPan = (event) => {
panning = false;
}

const movePan = (thisX, thisY) => {
const dx = (thisX - lastX) * metersPerPixel;
const dy = (thisY - lastY) * metersPerPixel;
lastX = thisX;
lastY = thisY;

const target = modelViewer.getCameraTarget();
target.x += dx * panX[0] + dy * panY[0];
target.y += dx * panX[1] + dy * panY[1];
target.z += dx * panX[2] + dy * panY[2];
modelViewer.cameraTarget = `${target.x}m ${target.y}m ${target.z}m`;

// This pauses turntable rotation
modelViewer.dispatchEvent(new CustomEvent(
'camera-change', {detail: {source: 'user-interaction'}}));
};

const recenter = (pointer) => {
panning = false;
if (Math.abs(pointer.clientX - startX) > tapDistance ||
Math.abs(pointer.clientY - startY) > tapDistance)
return;
const hit = modelViewer.positionAndNormalFromPoint(pointer.clientX, pointer.clientY);
if(hit != null) {
modelViewer.cameraTarget = hit.position.toString();
}
else {
modelViewer.cameraTarget = 'auto auto auto';
modelViewer.cameraOrbit = 'auto auto auto';
}
};

modelViewer.addEventListener('mousedown', (event) => {
startX = event.clientX;
startY = event.clientY;
panning = event.button === 2 || event.ctrlKey || event.metaKey ||
event.shiftKey;
if (!panning)
return;

lastX = startX;
lastY = startY;
startPan();
event.stopPropagation();
}, true);

modelViewer.addEventListener('touchstart', (event) => {
const {targetTouches, touches} = event;
startX = targetTouches[0].clientX;
startY = targetTouches[0].clientY;
panning = targetTouches.length === 2 && targetTouches.length === touches.length;
if (!panning)
return;

lastX = 0.5 * (targetTouches[0].clientX + targetTouches[1].clientX);
lastY = 0.5 * (targetTouches[0].clientY + targetTouches[1].clientY);
startPan();
}, true);

self.addEventListener('mousemove', (event) => {
if (!panning)
return;

movePan(event.clientX, event.clientY);
event.stopPropagation();
}, true);

modelViewer.addEventListener('touchmove', (event) => {
if (!panning || event.targetTouches.length !== 2)
return;

const {targetTouches} = event;
const thisX = 0.5 * (targetTouches[0].clientX + targetTouches[1].clientX);
const thisY = 0.5 * (targetTouches[0].clientY + targetTouches[1].clientY);
movePan(thisX, thisY);
}, true);

let lastMousedown = {time: new Date().getTime()};
let lastTouchstart = {time: new Date().getTime()};
function doubletap(timer) {
var now = new Date().getTime();
var timesince = now - timer.time;
timer.time = new Date().getTime();
if((timesince < 400) && (timesince > 0))
return true;
else
return false;
}

self.addEventListener('mouseup', (event) => {
stopPan(event);
}, true);

modelViewer.addEventListener('oncontextmenu', (event) => {
return false;
});

self.addEventListener('mousedown', (event) => {
if(doubletap(lastMousedown))
recenter(event);
}, true);

modelViewer.addEventListener('touchstart', (event) => {
if (doubletap(lastTouchstart) && event.targetTouches.length === 0) {
recenter(event.changedTouches[0]);

if (event.cancelable) {
event.preventDefault();
}
}
}, true);
</script>
</template>
</example-snippet>
</div>
</div>
</div>

<div class="footer">
<ul>
<li class="attribution">
<a href="https://poly.google.com/view/dLHpzNdygsg">Astronaut</a> by <a href="https://poly.google.com/user/4aEd8rQgKu2">Poly</a>,
licensed under <a href="https://creativecommons.org/licenses/by/2.0/">CC-BY</a>.
</li>
<li class="attribution">
<a href="https://sketchfab.com/3d-models/thor-and-the-midgard-serpent-2ef4c45caa35450db1b876a7f94ff79d">Thor and the Midgard Serpent</a> by <a href="https://sketchfab.com/MatthijsDeRijk">Matthijs de Rijk</a>,
licensed under <a href="https://creativecommons.org/licenses/by/4.0/">CC-BY</a>.
</li>
</ul>
<div style="margin-top:24px;" class="copyright">©Copyright 2019 Google Inc. Licensed under the Apache License 2.0.</div>
Expand Down

0 comments on commit a715f7f

Please sign in to comment.