demo-int-table/front/src/components/Promo/load_models.vue

448 lines
15 KiB
Vue

<script setup lang="ts">
import { onMounted, onUnmounted, reactive, ref, watch } from 'vue';
import {
Box3, Color, Group, Mesh,
MeshStandardMaterial, MeshBasicMaterial,
Vector2, Vector3,
Quaternion, AdditiveBlending,
Euler, SRGBColorSpace, RingGeometry,
CircleGeometry,
} from 'three';
import { useTresContext, useSeek, useTexture, useLoop } from '@tresjs/core';
// @ts-ignore
import { useGLTF } from '@tresjs/cientos'
import Env from './env.vue'
import { IMAGE_URL, PROMOBG, SERVER_URL, } from '../../constants'
import { usePromoSidebar } from '../../stores/promo_sidebar';
import { usePromoScene } from '../../stores/promo_scene';
import { useClickable } from '../../stores/clickable';
import { useLoading } from '../../stores/loading';
import { mobileAndTabletCheck } from '../../helpers';
import { useTimer } from '../../stores/timer';
import { useRawData } from '../../stores/raw_data';
import { useTimerHome } from '../../stores/timer_home';
import { useItem } from '../../stores/item';
import { hsl } from 'd3';
const props = defineProps(['source', 'loaded_pan', 'push'])
const models = ref<model3DType[]>([])
const clickable_items = ref<any[]>([])
const clickable_refs = ref<any[]>([])
const envVars = reactive({}) as EnvVars
const process_loading = ref(null)
const targetDistance = reactive({ max: 10, min: 0 })
let sidebar_clickable = [] as PromoScene[]
let sidebar_visible = [] as PromoScene[]
const COUNT = 50
type smooth = {
value: Vector3 | Quaternion | undefined,
count: number
}
const smooth_target = reactive({}) as smooth
const smooth_move = reactive({}) as smooth
const smooth_rotate = reactive({}) as smooth
const temp_smooth_target = reactive({}) as smooth
const temp_smooth_move = reactive({}) as smooth
const temp_smooth_rotate = reactive({}) as smooth
const sidebar = usePromoSidebar();
const sidebar_scene = usePromoScene();
const clickable = useClickable()
const loading_store = useLoading()
const raw_dataStore = useRawData();
const itemStore = useItem();
const { controls, camera, scene, raycaster, renderer } = useTresContext()
const { seekByName, seekAllByName } = useSeek()
const { scene: point_mesh_obj } = await useGLTF((await import('../../assets/pointer/point-v1-v1.glb')).default)
const set_moveto = (obj: smooth, value: Vector3 | Quaternion | undefined) => {
obj.value = value
obj.count = COUNT
}
const setEnv = async () => {
envVars.focus = raw_dataStore.data.max_distance * 0.5
if (raw_dataStore.data.env) {
Object.assign(envVars, raw_dataStore.data.env)
} else {
envVars.clear_color = PROMOBG
}
}
const setControls = () => {
if (!controls.value) return;
controls.value.enabled = false;
targetDistance.max = raw_dataStore.data.max_distance;
targetDistance.min = mobileAndTabletCheck() ? raw_dataStore.data.min_distance * 0.5 : raw_dataStore.data.min_distance;
// (controls.value as any).minDistance = mobileAndTabletCheck() ? raw_dataStore.data.min_distance * 0.5 : raw_dataStore.data.min_distance;
(controls.value as any).maxDistance = raw_dataStore.data.max_distance;
const d = targetDistance.max * 0.5
camera.value?.position.set(d, d, d);
(controls.value as any).target = new Vector3(0, 0, 0);
(controls.value as any).autoRotate = false;
(controls.value as any)._needsUpdate = true;
(controls.value as any).update()
}
const clearValues = () => {
clickable_items.value = []
clickable_refs.value = []
sidebar_clickable = []
sidebar_visible = []
}
const loadModels = async () => {
if (!props.source) return
if (!raw_dataStore.data) return
console.log(`load models ${props.source} ${process_loading.value}`)
clearValues()
loading_store.status = 'loading'
process_loading.value = props.source
await raw_dataStore.load(props)
raw_dataStore.data.loading = true
loading_store.status = 'env'
await setEnv()
loading_store.status = 'other'
setControls()
sidebar_scene.setName({ name: raw_dataStore.data.name, description: raw_dataStore.data.name })
loading_store.status = 'model'
for (let index = 0; index < raw_dataStore.data.elements.length; index++) {
if (process_loading.value !== props.source) return
loading_store.count = index
const element = raw_dataStore.data.elements[index];
const item = {} as model3DType
item.modelUrl = `${IMAGE_URL}/${element.model_file}`
let { scene: loaded_scene } = await useGLTF(item.modelUrl)
item.modelFile = loaded_scene
item.id = element.id
item.name = element.name
if (!element.is_enabled) {
item.modelFile.visible = false
}
if (item.modelFile.children[0]) {
item.modelFile.children[0].position.set(
item.modelFile.children[0].position.x + element.x_pos,
item.modelFile.children[0].position.y + element.y_pos,
item.modelFile.children[0].position.z + element.z_pos
)
item.modelFile.children[0].updateMatrixWorld(true)
}
item.modelFile.traverse((el: Mesh) => {
if (el.material && el.material instanceof MeshStandardMaterial && el.material.map) {
el.material.map.colorSpace = SRGBColorSpace
}
})
models.value.push(item)
const res = await fetch(`${SERVER_URL}/api/obj/clickable/?source=${element.id}`)
const clickable_areas = await res.json()
clickable.list.push(...clickable_areas)
if (!element.can_not_disable) {
sidebar_visible.push(element)
}
}
sidebar_scene.setVisible(sidebar_visible)
if (!models.value.find(el => el.name == 'ground')) {
loading_store.status = 'ground'
}
for (let index = 0; index < clickable.list.length; index++) {
loading_store.status = 'clickable'
const element = clickable.list[index];
const find_element = seekByName(scene.value, element.object_name)
if (!find_element) continue
if (find_element && !(find_element as Group).isGroup) {
const world_position = new Vector3();
const size = new Vector3()
const boundingBox = ((find_element as Mesh).geometry.boundingBox as any)
boundingBox.getCenter(world_position);
boundingBox.getCenter(size);
(find_element as Mesh).localToWorld(world_position);
const point = point_mesh_obj.clone()
const point_color = new Color(envVars.clear_color).offsetHSL(0.45, 0.2, 0)
point.traverse((el: Group | Mesh) => {
if (el instanceof Mesh && el.material && el.material instanceof MeshStandardMaterial) {
el.position.set(0, 0, 0)
el.material.color = point_color
}
})
point.position.set(world_position.x, world_position.y, world_position.z)
console.log(world_position)
point.updateMatrixWorld()
point.name = `${element.id}_clickable`
if (clickable_items.value.find(el => el.name == point.name)) continue
clickable_items.value.push(point)
clickable_refs.value.push(ref(`${element.id}_clickable`))
sidebar_clickable.push({
is_enabled: true,
id: element.id,
name: element.name
})
}
}
sidebar_scene.setClickable(sidebar_clickable)
loading_store.status = 'boxes'
const box = new Box3();
models.value.forEach(element => {
if (element.name !== 'ground') {
box.expandByObject(element.modelFile.clone());
}
});
const box_size = new Vector3();
box.getSize(box_size);
props.loaded_pan(
new Vector3(box_size.x * 0.5, box_size.y * 0.5, box_size.z * 0.5),
new Vector3(box_size.x * -0.25, box_size.y * -0.25, box_size.z * -0.25),
);
(controls.value as any).enabled = true;
raw_dataStore.data.loading = false;
timer.startTimer()
if (controls.value && (controls.value as any).autoRotate) {
(controls.value as any).autoRotate = false;
}
// process_loading.value = null
}
const gotoCenterAndDistance = () => {
targetDistance.min = mobileAndTabletCheck() ? raw_dataStore.data.min_distance * 0.5 : raw_dataStore.data.min_distance;
targetDistance.max = raw_dataStore.data.max_distance;
set_moveto(smooth_target, new Vector3(0, 0, 0))
set_moveto(smooth_move, new Vector3(
raw_dataStore.data.max_distance * 0.5,
raw_dataStore.data.max_distance * 0.5,
raw_dataStore.data.max_distance * 0.5
))
}
watch(() => props.source, () => {
if (props.source) {
raw_dataStore.$reset()
const loaded = seekByName(scene.value, 'loaded')
if (loaded) {
loaded.children = []
}
console.log('props change')
sidebar.close()
// loadModels()
} else {
renderer.value.dispose()
}
}, { deep: true })
watch(() => sidebar.is_open, () => {
if (sidebar.is_open == false) {
gotoCenterAndDistance();
}
})
watch(() => sidebar.is_open && sidebar.id_clickable, () => {
if (sidebar.is_open && sidebar.id_clickable) {
const clickable = useClickable()
const target = clickable.list.find(el => el.id == sidebar.id_clickable)
if (!target) return
const el = seekByName(scene.value, `${sidebar.id_clickable}_clickable`);
if (el) {
targetDistance.max = 10
targetDistance.min = 1
const target_vector = new Vector3();
el.getWorldPosition(target_vector);
// target_vector.y = raw_dataStore.data.min_distance > 50 ? raw_dataStore.data.min_distance / 10 : 1;
const quaternion = new Quaternion();
quaternion.setFromEuler(new Euler(
-45 * 1 * (Math.PI / 180),
35 * 1 * (Math.PI / 180),
35 * 1 * (Math.PI / 180)
));
set_moveto(smooth_rotate, quaternion)
const d = raw_dataStore.data.max_distance * 0.5
if (camera.value?.position && camera.value?.position.distanceTo(new Vector3(d, d, d)) > 20) {
set_moveto(temp_smooth_target, target_vector)
set_moveto(temp_smooth_move, target_vector)
set_moveto(smooth_target, new Vector3(0, 0, 0))
set_moveto(smooth_move, new Vector3(d, d, d))
} else {
set_moveto(smooth_target, target_vector)
set_moveto(smooth_move, target_vector)
}
}
}
}, { deep: true })
const { onAfterRender } = useLoop()
onAfterRender(() => {
clickable_refs.value.map(el => {
if (el.value[0] && el.value[0].children) {
el.value[0].children[0].lookAt(camera.value?.position);
el.value[0].children[0].rotateX(90 * (Math.PI / 180));
el.value[0].children[0].rotateZ(15 * (Math.PI / 180));
const dis_to_cam = camera.value?.position.distanceTo(el.value[0].position);
if (dis_to_cam) {
const scaling = (0.5 * dis_to_cam) / 100
el.value[0].children[0].scale.set(scaling, scaling, scaling);
el.value[0].updateMatrixWorld()
}
}
})
const koef = (1 / COUNT) * 3
const smoothy = [
{
el: smooth_target,
temp_el: temp_smooth_target,
f: () => (controls.value as any).target.lerp(smooth_target.value as Vector3, smooth_target.count < COUNT * 0.5 ? 0.15 : koef),
},
{
el: smooth_move,
temp_el: temp_smooth_move,
f: () => camera.value?.position.lerp(smooth_move.value as Vector3, smooth_move.count < COUNT * 0.5 ? 0.15 : koef),
},
{
el: smooth_rotate,
f: () => {
camera.value?.quaternion.slerp(smooth_rotate.value as Quaternion, koef);
camera.value?.quaternion.normalize()
}
}
]
smoothy.forEach(element => {
if (element.el.value) {
timer.resetTimer();
element.f()
element.el.count -= 1
if (element.el.count == 0) {
set_moveto(element.el, undefined)
if (element.temp_el && element.temp_el.value) {
set_moveto(element.el, element.temp_el.value)
set_moveto(element.temp_el, undefined)
}
}
}
});
})
const timer = useTimer()
timer.timer_func = () => {
if (timer.seconds_left == 0 && !(controls.value as any).autoRotate && (controls.value as any).enabled) {
gotoCenterAndDistance();
sidebar.close();
(controls.value as any).autoRotate = true;
(controls.value as any).autoRotateSpeed = 0.5;
timer.startTimer()
home_timer.startTimer()
}
}
const stopTimer = () => {
timer.resetTimer()
home_timer.stopTimer()
if ((controls.value as any).autoRotate) {
(controls.value as any).autoRotate = false
}
}
const home_timer = useTimerHome()
home_timer.timer_func = () => {
if (itemStore.page.scene_3d !== raw_dataStore.data.id) {
props.push(`/${itemStore.page.slug}/${itemStore.page.scene_3d}`)
}
}
const pointer = reactive({ x: 0, y: 0 })
const clickEvent = (event: MouseEvent) => {
if (event.target && !(event.target as HTMLElement).closest('canvas')) return
const x = (event.clientX / window.innerWidth) * 2 - 1
const y = - (event.clientY / window.innerHeight) * 2 + 1
if (x == pointer.x && y == pointer.y) return
if (!camera.value) return
pointer.x = x
pointer.y = y
raycaster.value.setFromCamera(new Vector2(pointer.x, pointer.y), camera.value);
const clickable_objects = seekAllByName(scene.value, '_clickable');
const intersects = raycaster.value.intersectObjects(clickable_objects);
const names = intersects
.map(el => (el.object.parent ? el.object.parent.name : el.object.name) ?? false)
.filter(Boolean)
if (names.length) {
sidebar.open(parseInt(names[0].replace('_clickable', '')))
sidebar.toggleAccordion('clickable')
}
}
const timerEvent = ['click', 'contextmenu', 'mousedown', 'mouseup', 'touchstart', 'touchend', 'touchmove']
onMounted(() => {
console.log('mount')
clearValues()
loadModels()
document.addEventListener('click', clickEvent)
timerEvent.map((event: string) => {
document.addEventListener(event, stopTimer)
})
if (sidebar.is_open) {
sidebar.close()
}
})
onUnmounted(() => {
console.log('unmount load models')
clearValues()
document.removeEventListener('click', clickEvent)
timerEvent.map((event: string) => {
document.removeEventListener(event, stopTimer)
})
renderer.value.dispose()
})
</script>
<template>
<TresGroup name="loaded" :key="props.source" ref="loaded">
<Env v-bind="envVars" />
<!-- <PostProcessing /> -->
<template v-for="item in models">
<TresGroup :name="item.name"
:visible="sidebar_scene.visible.find(el => el.id == item.id)?.is_enabled ?? true">
<TresObject3D v-bind="item.modelFile.clone()" />
</TresGroup>
</template>
<template v-for="(item, i) in clickable_items">
<TresObject3D v-bind="item.clone()" :ref="clickable_refs[i]" />
</template>
</TresGroup>
</template>