serve viewer

This commit is contained in:
Robin Appelman 2024-09-02 14:45:06 +02:00
commit da0ef79fa1
10 changed files with 126 additions and 96 deletions

View file

@ -7,7 +7,9 @@ use axum::response::{Html, IntoResponse, Response};
use axum::routing::get;
use axum::Router;
use clap::Parser;
use http::Method;
use http::header::{CACHE_CONTROL, CONTENT_TYPE, ETAG};
use http::{HeaderValue, Method};
use include_dir::{include_dir, Dir};
use reqwest::Client;
use serde::Deserialize;
use std::fs::{read, read_to_string, write};
@ -144,6 +146,9 @@ async fn main() -> Result<()> {
let app = Router::new()
.route("/gltf/:map", get(convert))
.route("/view/:map", get(view))
.route("/assets/:asset", get(viewer_asset))
.route("/transcoders/:asset", get(transcoder_asset))
.route("/", get(index))
.layer(cors)
.with_state(Arc::new(app));
@ -218,6 +223,87 @@ async fn index() -> impl IntoResponse {
Html(include_str!("./index.html"))
}
async fn view() -> impl IntoResponse {
Html(include_str!("./viewer/dist/index.html"))
}
static VIEWER_ASSETS: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/src/server/viewer/dist/assets");
pub async fn viewer_asset(asset: Path<String>) -> impl IntoResponse {
let path = asset.as_str();
match VIEWER_ASSETS.get_file(path) {
Some(file) => (
[
(
CONTENT_TYPE,
HeaderValue::from_str(guess_mime(path)).unwrap(),
),
(
ETAG,
HeaderValue::from_static("these_should_be_fully_static"),
),
(
CACHE_CONTROL,
HeaderValue::from_static("public, max-age=2592000, immutable"),
),
],
file.contents(),
)
.into_response(),
None => StatusCode::NOT_FOUND.into_response(),
}
}
static TRANSCODER_ASSETS: Dir<'_> =
include_dir!("$CARGO_MANIFEST_DIR/src/server/viewer/transcoders");
pub async fn transcoder_asset(asset: Path<String>) -> impl IntoResponse {
let path = asset.as_str();
match TRANSCODER_ASSETS.get_file(path) {
Some(file) => (
[
(
CONTENT_TYPE,
HeaderValue::from_str(guess_mime(path)).unwrap(),
),
(
ETAG,
HeaderValue::from_static("these_should_be_fully_static"),
),
(
CACHE_CONTROL,
HeaderValue::from_static("public, max-age=2592000, immutable"),
),
],
file.contents(),
)
.into_response(),
None => StatusCode::NOT_FOUND.into_response(),
}
}
pub fn guess_mime(path: &str) -> &'static str {
if path.ends_with("svg") {
"image/svg+xml"
} else if path.ends_with("png") {
"image/png"
} else if path.ends_with("webp") {
"image/webp"
} else if path.ends_with("css") {
"text/css"
} else if path.ends_with("wasm") {
"application/wasm"
} else if path.ends_with("js")
|| path.ends_with("ts")
|| path.ends_with("jsx")
|| path.ends_with("tsx")
{
"text/javascript"
} else {
"text/plain"
}
}
async fn convert(
State(app): State<Arc<App>>,
Path(map): Path<String>,

View file

@ -1,83 +0,0 @@
import * as THREE from 'three'
import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls'
import {GLTFLoader} from 'three/examples/jsm/loaders/GLTFLoader'
import Stats from 'three/examples/jsm/libs/stats.module'
const scene = new THREE.Scene()
scene.add(new THREE.AxesHelper(5))
const light = new THREE.SpotLight(0xffffff, Math.PI * 20)
light.position.set(5, 5, 5)
scene.add(light)
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
camera.position.z = 2
const renderer = new THREE.WebGLRenderer()
// Since Three r150, lighting has changed significantly with every version up to three r158
// keep the threejs defaults, and reduce light watts in blender if using punctual lights
// if using Threejs lights, then you need to experiment with light intensity.
// renderer.physicallyCorrectLights = true //deprecated
// renderer.useLegacyLights = false //deprecated
renderer.shadowMap.enabled = true
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)
const controls = new OrbitControls(camera, renderer.domElement)
controls.enableDamping = true
const loader = new GLTFLoader()
loader.load(
'models/monkey.glb',
function (gltf) {
// gltf.scene.traverse(function (child) {
// if ((child as THREE.Mesh).isMesh) {
// const m = (child as THREE.Mesh)
// m.receiveShadow = true
// m.castShadow = true
// }
// if (((child as THREE.Light)).isLight) {
// const l = (child as THREE.SpotLight)
// l.castShadow = true
// l.shadow.bias = -.003
// l.shadow.mapSize.width = 2048
// l.shadow.mapSize.height = 2048
// }
// })
scene.add(gltf.scene)
},
(xhr) => {
console.log((xhr.loaded / xhr.total) * 100 + '% loaded')
},
(error) => {
console.log(error)
}
)
window.addEventListener('resize', onWindowResize, false)
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight
camera.updateProjectionMatrix()
renderer.setSize(window.innerWidth, window.innerHeight)
render()
}
const stats = new Stats()
document.body.appendChild(stats.dom)
function animate() {
requestAnimationFrame(animate)
controls.update()
render()
stats.update()
}
function render() {
renderer.render(scene, camera)
}
animate()

View file

@ -3,7 +3,7 @@
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Three.js TypeScript Tutorials by Sean Bradley : https://sbcode.net/threejs</title>
<title>TF2 Map viewer</title>
</head>
<body class="loading">

View file

@ -1,7 +1,6 @@
import './style.css'
import * as THREE from 'three'
import {PointerLockControls} from 'three/addons/controls/PointerLockControls.js'
// import {OrbitControls} from 'three/addons/controls/OrbitControls.js'
import {GLTFLoader} from 'three/addons/loaders/GLTFLoader.js'
import Stats from 'three/addons/libs/stats.module.js'
import {KTX2Loader} from 'three/addons/loaders/KTX2Loader.js';
@ -17,7 +16,7 @@ const renderer = new THREE.WebGLRenderer({antialias: true})
const loader = new GLTFLoader();
const ktx2Loader = new KTX2Loader();
ktx2Loader.setTranscoderPath('transcoders/basis/');
ktx2Loader.setTranscoderPath('/transcoders/');
ktx2Loader.detectSupport(renderer);
loader.setKTX2Loader(ktx2Loader);
@ -56,15 +55,23 @@ const dirLight = new THREE.DirectionalLight(0xefefff, 1.5);
dirLight.position.set(10, 10, 10);
scene.add(dirLight);
let map;
const urlParams = new URLSearchParams(window.location.search);
const map = urlParams.get('map');
if (window.location.pathname.startsWith('/view/')) {
map = window.location.pathname.substring('/view/'.length);
} else {
map = urlParams.get('map');
}
const textureScale = urlParams.get('texture_scale') || 0.25;
const textures = urlParams.get('textures') || true;
console.log(map);
loader.load(`https://gltf.demos.tf/gltf/${map}.glb?texture_scale=0.25`, (gltf) => {
loader.load(`/gltf/${map}.glb?texture_scale=${textureScale}&textures=${textures}`, (gltf) => {
document.body.classList.remove('loading');
gltf.scene.traverse(child => {
if (child.material) {
child.material.metalness = 0;
if ((child as THREE.Mesh).material) {
((child as THREE.Mesh).material as any as THREE.MaterialJSON).metalness = 0;
}
});
scene.add(gltf.scene)
@ -88,7 +95,7 @@ let movementScale = 250;
const _vector = new THREE.Vector3();
function moveForward(distance) {
function moveForward(distance: number): void {
_vector.copy(controls.getDirection(_vector));
camera.position.addScaledVector(_vector, distance);
@ -125,8 +132,6 @@ function animate() {
movementScale = 250;
}
controls.update()
renderer.render(scene, camera)
stats.update()