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.
299 lines
8.2 KiB
299 lines
8.2 KiB
2 years ago
|
<!DOCTYPE html>
|
||
|
<html lang="en">
|
||
|
<head>
|
||
|
<title>three.js webgl - arcball controls</title>
|
||
|
<meta charset="utf-8">
|
||
|
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
|
||
|
<link type="text/css" rel="stylesheet" href="main.css">
|
||
|
|
||
|
</head>
|
||
|
|
||
|
<body>
|
||
|
<div id="info">
|
||
|
<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - arcball controls<br/>
|
||
|
<a href="http://www.polycount.com/forum/showthread.php?t=130641" target="_blank" rel="noopener">Cerberus(FFVII Gun) model</a> by Andrew Maximov.
|
||
|
</div>
|
||
|
|
||
|
<!-- 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",
|
||
|
"three/addons/": "./jsm/"
|
||
|
}
|
||
|
}
|
||
|
</script>
|
||
|
|
||
|
<script type="module">
|
||
|
import * as THREE from 'three';
|
||
|
|
||
|
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
|
||
|
|
||
|
import { ArcballControls } from 'three/addons/controls/ArcballControls.js';
|
||
|
|
||
|
import { OBJLoader } from 'three/addons/loaders/OBJLoader.js';
|
||
|
import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
|
||
|
|
||
|
const cameras = [ 'Orthographic', 'Perspective' ];
|
||
|
const cameraType = { type: 'Perspective' };
|
||
|
|
||
|
const perspectiveDistance = 2.5;
|
||
|
const orthographicDistance = 120;
|
||
|
let camera, controls, scene, renderer, gui;
|
||
|
let folderOptions, folderAnimations;
|
||
|
|
||
|
const arcballGui = {
|
||
|
|
||
|
gizmoVisible: true,
|
||
|
|
||
|
setArcballControls: function () {
|
||
|
|
||
|
controls = new ArcballControls( camera, renderer.domElement, scene );
|
||
|
controls.addEventListener( 'change', render );
|
||
|
|
||
|
this.gizmoVisible = true;
|
||
|
|
||
|
this.populateGui();
|
||
|
|
||
|
},
|
||
|
|
||
|
populateGui: function () {
|
||
|
|
||
|
folderOptions.add( controls, 'enabled' ).name( 'Enable controls' );
|
||
|
folderOptions.add( controls, 'enableGrid' ).name( 'Enable Grid' );
|
||
|
folderOptions.add( controls, 'enableRotate' ).name( 'Enable rotate' );
|
||
|
folderOptions.add( controls, 'enablePan' ).name( 'Enable pan' );
|
||
|
folderOptions.add( controls, 'enableZoom' ).name( 'Enable zoom' );
|
||
|
folderOptions.add( controls, 'cursorZoom' ).name( 'Cursor zoom' );
|
||
|
folderOptions.add( controls, 'adjustNearFar' ).name( 'adjust near/far' );
|
||
|
folderOptions.add( controls, 'scaleFactor', 1.1, 10, 0.1 ).name( 'Scale factor' );
|
||
|
folderOptions.add( controls, 'minDistance', 0, 50, 0.5 ).name( 'Min distance' );
|
||
|
folderOptions.add( controls, 'maxDistance', 0, 50, 0.5 ).name( 'Max distance' );
|
||
|
folderOptions.add( controls, 'minZoom', 0, 50, 0.5 ).name( 'Min zoom' );
|
||
|
folderOptions.add( controls, 'maxZoom', 0, 50, 0.5 ).name( 'Max zoom' );
|
||
|
folderOptions.add( arcballGui, 'gizmoVisible' ).name( 'Show gizmos' ).onChange( function () {
|
||
|
|
||
|
controls.setGizmosVisible( arcballGui.gizmoVisible );
|
||
|
|
||
|
} );
|
||
|
folderOptions.add( controls, 'copyState' ).name( 'Copy state(ctrl+c)' );
|
||
|
folderOptions.add( controls, 'pasteState' ).name( 'Paste state(ctrl+v)' );
|
||
|
folderOptions.add( controls, 'reset' ).name( 'Reset' );
|
||
|
folderAnimations.add( controls, 'enableAnimations' ).name( 'Enable anim.' );
|
||
|
folderAnimations.add( controls, 'dampingFactor', 0, 100, 1 ).name( 'Damping' );
|
||
|
folderAnimations.add( controls, 'wMax', 0, 100, 1 ).name( 'Angular spd' );
|
||
|
|
||
|
}
|
||
|
|
||
|
};
|
||
|
|
||
|
|
||
|
init();
|
||
|
|
||
|
function init() {
|
||
|
|
||
|
const container = document.createElement( 'div' );
|
||
|
document.body.appendChild( container );
|
||
|
|
||
|
renderer = new THREE.WebGLRenderer( { antialias: true, alpha: true } );
|
||
|
renderer.setPixelRatio( window.devicePixelRatio );
|
||
|
renderer.setSize( window.innerWidth, window.innerHeight );
|
||
|
|
||
|
renderer.outputEncoding = THREE.sRGBEncoding;
|
||
|
renderer.toneMapping = THREE.ReinhardToneMapping;
|
||
|
renderer.toneMappingExposure = 3;
|
||
|
renderer.domElement.style.background = 'linear-gradient( 180deg, rgba( 0,0,0,1 ) 0%, rgba( 128,128,255,1 ) 100% )';
|
||
|
container.appendChild( renderer.domElement );
|
||
|
|
||
|
//
|
||
|
|
||
|
scene = new THREE.Scene();
|
||
|
scene.add( new THREE.HemisphereLight( 0x443333, 0x222233, 4 ) );
|
||
|
|
||
|
camera = makePerspectiveCamera();
|
||
|
camera.position.set( 0, 0, perspectiveDistance );
|
||
|
|
||
|
const material = new THREE.MeshStandardMaterial();
|
||
|
|
||
|
new OBJLoader()
|
||
|
.setPath( 'models/obj/cerberus/' )
|
||
|
.load( 'Cerberus.obj', function ( group ) {
|
||
|
|
||
|
const textureLoader = new THREE.TextureLoader().setPath( 'models/obj/cerberus/' );
|
||
|
|
||
|
material.roughness = 1;
|
||
|
material.metalness = 1;
|
||
|
|
||
|
const diffuseMap = textureLoader.load( 'Cerberus_A.jpg', render );
|
||
|
diffuseMap.encoding = THREE.sRGBEncoding;
|
||
|
material.map = diffuseMap;
|
||
|
|
||
|
material.metalnessMap = material.roughnessMap = textureLoader.load( 'Cerberus_RM.jpg', render );
|
||
|
material.normalMap = textureLoader.load( 'Cerberus_N.jpg', render );
|
||
|
|
||
|
material.map.wrapS = THREE.RepeatWrapping;
|
||
|
material.roughnessMap.wrapS = THREE.RepeatWrapping;
|
||
|
material.metalnessMap.wrapS = THREE.RepeatWrapping;
|
||
|
material.normalMap.wrapS = THREE.RepeatWrapping;
|
||
|
|
||
|
|
||
|
group.traverse( function ( child ) {
|
||
|
|
||
|
if ( child.isMesh ) {
|
||
|
|
||
|
child.material = material;
|
||
|
|
||
|
}
|
||
|
|
||
|
} );
|
||
|
|
||
|
group.rotation.y = Math.PI / 2;
|
||
|
group.position.x += 0.25;
|
||
|
scene.add( group );
|
||
|
render();
|
||
|
|
||
|
new RGBELoader()
|
||
|
.setPath( 'textures/equirectangular/' )
|
||
|
.load( 'venice_sunset_1k.hdr', function ( hdrEquirect ) {
|
||
|
|
||
|
hdrEquirect.mapping = THREE.EquirectangularReflectionMapping;
|
||
|
|
||
|
scene.environment = hdrEquirect;
|
||
|
|
||
|
render();
|
||
|
|
||
|
} );
|
||
|
|
||
|
|
||
|
window.addEventListener( 'keydown', onKeyDown );
|
||
|
window.addEventListener( 'resize', onWindowResize );
|
||
|
|
||
|
//
|
||
|
|
||
|
gui = new GUI();
|
||
|
gui.add( cameraType, 'type', cameras ).name( 'Choose Camera' ).onChange( function () {
|
||
|
|
||
|
setCamera( cameraType.type );
|
||
|
|
||
|
} );
|
||
|
|
||
|
folderOptions = gui.addFolder( 'Arcball parameters' );
|
||
|
folderAnimations = folderOptions.addFolder( 'Animations' );
|
||
|
|
||
|
arcballGui.setArcballControls();
|
||
|
|
||
|
render();
|
||
|
|
||
|
} );
|
||
|
|
||
|
}
|
||
|
|
||
|
function makeOrthographicCamera() {
|
||
|
|
||
|
const halfFovV = THREE.MathUtils.DEG2RAD * 45 * 0.5;
|
||
|
const halfFovH = Math.atan( ( window.innerWidth / window.innerHeight ) * Math.tan( halfFovV ) );
|
||
|
|
||
|
const halfW = perspectiveDistance * Math.tan( halfFovH );
|
||
|
const halfH = perspectiveDistance * Math.tan( halfFovV );
|
||
|
const near = 0.01;
|
||
|
const far = 2000;
|
||
|
const newCamera = new THREE.OrthographicCamera( - halfW, halfW, halfH, - halfH, near, far );
|
||
|
return newCamera;
|
||
|
|
||
|
}
|
||
|
|
||
|
function makePerspectiveCamera() {
|
||
|
|
||
|
const fov = 45;
|
||
|
const aspect = window.innerWidth / window.innerHeight;
|
||
|
const near = 0.01;
|
||
|
const far = 2000;
|
||
|
const newCamera = new THREE.PerspectiveCamera( fov, aspect, near, far );
|
||
|
return newCamera;
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
function onWindowResize() {
|
||
|
|
||
|
if ( camera.type == 'OrthographicCamera' ) {
|
||
|
|
||
|
const halfFovV = THREE.MathUtils.DEG2RAD * 45 * 0.5;
|
||
|
const halfFovH = Math.atan( ( window.innerWidth / window.innerHeight ) * Math.tan( halfFovV ) );
|
||
|
|
||
|
const halfW = perspectiveDistance * Math.tan( halfFovH );
|
||
|
const halfH = perspectiveDistance * Math.tan( halfFovV );
|
||
|
camera.left = - halfW;
|
||
|
camera.right = halfW;
|
||
|
camera.top = halfH;
|
||
|
camera.bottom = - halfH;
|
||
|
|
||
|
} else if ( camera.type == 'PerspectiveCamera' ) {
|
||
|
|
||
|
camera.aspect = window.innerWidth / window.innerHeight;
|
||
|
|
||
|
}
|
||
|
|
||
|
camera.updateProjectionMatrix();
|
||
|
|
||
|
renderer.setSize( window.innerWidth, window.innerHeight );
|
||
|
|
||
|
render();
|
||
|
|
||
|
}
|
||
|
|
||
|
function render() {
|
||
|
|
||
|
renderer.render( scene, camera );
|
||
|
|
||
|
}
|
||
|
|
||
|
function onKeyDown( event ) {
|
||
|
|
||
|
if ( event.key === 'c' ) {
|
||
|
|
||
|
if ( event.ctrlKey || event.metaKey ) {
|
||
|
|
||
|
controls.copyState();
|
||
|
|
||
|
}
|
||
|
|
||
|
} else if ( event.key === 'v' ) {
|
||
|
|
||
|
if ( event.ctrlKey || event.metaKey ) {
|
||
|
|
||
|
controls.pasteState();
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
function setCamera( type ) {
|
||
|
|
||
|
if ( type == 'Orthographic' ) {
|
||
|
|
||
|
camera = makeOrthographicCamera();
|
||
|
camera.position.set( 0, 0, orthographicDistance );
|
||
|
|
||
|
|
||
|
} else if ( type == 'Perspective' ) {
|
||
|
|
||
|
camera = makePerspectiveCamera();
|
||
|
camera.position.set( 0, 0, perspectiveDistance );
|
||
|
|
||
|
}
|
||
|
|
||
|
controls.setCamera( camera );
|
||
|
|
||
|
render();
|
||
|
|
||
|
}
|
||
|
|
||
|
</script>
|
||
|
</body>
|