Merge branch 'bx-906-front'

This commit is contained in:
Kseninia Mikhaylova 2024-06-28 16:39:28 +03:00
commit 48e4d91431
7 changed files with 95 additions and 66 deletions

View File

@ -1100,7 +1100,7 @@
"kind": 6, "kind": 6,
"importPath": "back.object.models", "importPath": "back.object.models",
"description": "back.object.models", "description": "back.object.models",
"peekOfCode": "class Scene3D(models.Model):\n name = models.CharField(\n max_length=120,\n )\n elements = models.ManyToManyField(Element3D)\n min_distance = models.IntegerField(\n validators=[MinValueValidator(1), MaxValueValidator(200)], blank=True, null=True\n )\n max_distance = models.IntegerField(\n validators=[MinValueValidator(2), MaxValueValidator(500)], blank=True, null=True", "peekOfCode": "class Scene3D(models.Model):\n name = models.CharField(\n max_length=120,\n )\n elements = models.ManyToManyField(Element3D)\n min_distance = models.IntegerField(\n validators=[MinValueValidator(1), MaxValueValidator(600)], blank=True, null=True\n )\n max_distance = models.IntegerField(\n validators=[MinValueValidator(2), MaxValueValidator(1000)], blank=True, null=True",
"detail": "back.object.models", "detail": "back.object.models",
"documentation": {} "documentation": {}
}, },

View File

@ -29,10 +29,10 @@ class Scene3D(models.Model):
elements = models.ManyToManyField(Element3D) elements = models.ManyToManyField(Element3D)
min_distance = models.IntegerField( min_distance = models.IntegerField(
validators=[MinValueValidator(1), MaxValueValidator(200)], blank=True, null=True validators=[MinValueValidator(1), MaxValueValidator(600)], blank=True, null=True
) )
max_distance = models.IntegerField( max_distance = models.IntegerField(
validators=[MinValueValidator(2), MaxValueValidator(500)], blank=True, null=True validators=[MinValueValidator(2), MaxValueValidator(1000)], blank=True, null=True
) )
def __str__(self): def __str__(self):

View File

@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, onUnmounted, reactive, ref, watch } from 'vue'; import { ref, watch } from 'vue';
import { Box3, Vector2, Vector3 } from 'three'; import { Box3, Color, Group, Mesh, MeshStandardMaterial, PointLight, SphereGeometry, Vector3 } from 'three';
import { useTresContext, useSeek, useRenderLoop } from '@tresjs/core'; import { useTresContext, useSeek } from '@tresjs/core';
import { useGLTF } from '@tresjs/cientos' import { useGLTF } from '@tresjs/cientos'
import { IMAGE_URL, SERVER_URL, } from '../../constants' import { IMAGE_URL, SERVER_URL, } from '../../constants'
@ -20,8 +20,9 @@ function shadows_and_pos(scene: any) {
const models = ref<model3DType[]>([]) const models = ref<model3DType[]>([])
const clickable = ref<clickableAreaType[]>([]) const clickable = ref<clickableAreaType[]>([])
const clickable_objects = ref<any[]>([]) const clickable_objects = ref<any[]>([])
const clickable_items = ref<any[]>([])
const sidebar = usePromoSidebar(); const sidebar = usePromoSidebar();
const { controls, raycaster, camera, scene } = useTresContext() const { controls, camera, scene } = useTresContext()
const { seekByName } = useSeek() const { seekByName } = useSeek()
const loadModels = async () => { const loadModels = async () => {
@ -46,7 +47,6 @@ const loadModels = async () => {
let { scene: loaded_scene } = await useGLTF(item.modelUrl) let { scene: loaded_scene } = await useGLTF(item.modelUrl)
shadows_and_pos(loaded_scene) shadows_and_pos(loaded_scene)
item.modelFile = loaded_scene item.modelFile = loaded_scene
item.name = element.name item.name = element.name
models.value.push(item) models.value.push(item)
@ -60,7 +60,34 @@ const loadModels = async () => {
const element = clickable.value[index]; const element = clickable.value[index];
const find_element = seekByName(scene.value, element.object_name) const find_element = seekByName(scene.value, element.object_name)
if (!find_element) continue if (!find_element) continue
const res_array = find_element.isGroup ? find_element?.children : [find_element] const res_array = (find_element as Group).isGroup ? find_element?.children : [find_element]
if (find_element && !(find_element as Group).isGroup) {
const world_position = new Vector3();
(find_element as Mesh).geometry.boundingBox.getCenter(world_position);
(find_element as Mesh).localToWorld(world_position);
const light = new PointLight()
light.position.set(world_position.x, world_position.y * 5, world_position.z)
light.color = index % 2 ? new Color('red') : new Color('green');
light.power = 10000;
const point = new Mesh(new SphereGeometry(2, 16, 16), new MeshStandardMaterial({
color: light.color,
emissive: light.color,
emissiveIntensity: 100
}))
point.position.set(light.position.x, light.position.y, light.position.z)
point.name = `${element.id}_clickable`
// light.add(point)
clickable_items.value.push(light)
clickable_items.value.push(point)
clickable_objects.value.push({
name: point.name,
target: element.id,
object: point,
})
}
for (let index = 0; index < res_array.length; index++) { for (let index = 0; index < res_array.length; index++) {
const r = res_array[index]; const r = res_array[index];
let res = { let res = {
@ -82,52 +109,28 @@ const loadModels = async () => {
new Vector3(box_size.x * 0.5, box_size.y * 0.5, box_size.z * 0.5), new Vector3(box_size.x * 0.5, box_size.y * 0.5, box_size.z * 0.5),
new Vector3(box_size.x * -0.5, box_size.y * -0.5, box_size.z * -0.5), new Vector3(box_size.x * -0.5, box_size.y * -0.5, box_size.z * -0.5),
) )
console.log(box_size)
} }
controls.value.enabled = true; controls.value.enabled = true;
props.loaded() props.loaded()
} }
loadModels()
onMounted(() => { const openSidebar = (id: number) => {
document.addEventListener('click', clickEvent) const target = clickable.value.find(el => el.id == id)
}) if (!target) return
onUnmounted(() => { const sidebar_data = {
document.removeEventListener('click', clickEvent) title: target.name,
}) description: target.description
const pointer = reactive({ x: 0, y: 0 }) } as PromoSidebarData
const clickEvent = (event: MouseEvent) => { if (target?.target) {
console.time('raycaster') sidebar_data.target = target.target.toString()
const x = (event.clientX / window.innerWidth) * 2 - 1 sidebar_data.target_name = target.target_name
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 intersects = raycaster.value.intersectObjects(clickable_objects.value.map(el => el.object));
const names = intersects.map(el => el.object.name ?? false).filter(Boolean)
if (names.length) {
const clicks = clickable_objects.value.find(el => names.includes(el.name))
if (!clicks) return
const target = clickable.value.find(el => el.id == clicks.target)
if (!target) return
const sidebar_data = {
title: target.name,
description: target.description
} as PromoSidebarData
if (target?.target) {
sidebar_data.target = target.target.toString()
sidebar_data.target_name = target.target_name
}
sidebar.open()
sidebar.setData(sidebar_data)
} }
console.timeEnd('raycaster') sidebar.setData(sidebar_data)
sidebar.open()
} }
loadModels()
watch(() => props.source, () => { watch(() => props.source, () => {
const loaded = seekByName(scene.value, 'loaded') const loaded = seekByName(scene.value, 'loaded')
if (loaded) { if (loaded) {
@ -143,5 +146,9 @@ watch(() => props.source, () => {
<TresObject3D v-bind="item.modelFile.clone()" /> <TresObject3D v-bind="item.modelFile.clone()" />
</TresGroup> </TresGroup>
</template> </template>
<template v-for="item in clickable_items">
<TresObject3D v-if="item.type == 'PointLight'" v-bind="item.clone()" />
<TresMesh v-else @click="() => openSidebar(item.name.replace('_clickable', ''))" v-bind="item" />
</template>
</TresGroup> </TresGroup>
</template> </template>

View File

@ -3,9 +3,9 @@ import { reactive, ref, watch } from 'vue';
import type { Ref } from 'vue' import type { Ref } from 'vue'
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { PointLight, Vector3 } from 'three'; import { Vector3 } from 'three';
import { TresCanvas } from '@tresjs/core'; import { TresCanvas } from '@tresjs/core';
import { CameraControls, useProgress, StatsGl, OrbitControls, MapControls } from '@tresjs/cientos' import { StatsGl, OrbitControls } from '@tresjs/cientos'
import Env from './env.vue' import Env from './env.vue'
import LoadModels from './load_models.vue' import LoadModels from './load_models.vue'
@ -33,7 +33,7 @@ const camera = ref()
const cameraPosition = ref([1, 1, 1]) as unknown as Ref<Vector3> const cameraPosition = ref([1, 1, 1]) as unknown as Ref<Vector3>
const controlsState = reactive({ const controlsState = reactive({
maxPolarAngle: (Math.PI / 2) - 0.02, maxPolarAngle: (Math.PI / 2) - 0.05,
minAzimuthAngle: (Math.PI / 2) - 0.02, minAzimuthAngle: (Math.PI / 2) - 0.02,
}) })
const models_loading = ref(false) const models_loading = ref(false)
@ -41,14 +41,6 @@ const set_model_load_status = () => {
models_loading.value = !models_loading.value models_loading.value = !models_loading.value
} }
const point_light = new PointLight('#00d', 1000, 200)
point_light.intensity = 10000
point_light.position.set(-100, 100, 5)
point_light.castShadow = true
point_light.shadow.bias = -0.01
point_light.shadow.mapSize.width = 512 * 10
point_light.shadow.mapSize.height = 512 * 10
const route = useRoute() const route = useRoute()
const source = ref(route.params.target ? (route.params.target.toString() + '/') : '1/') const source = ref(route.params.target ? (route.params.target.toString() + '/') : '1/')
watch(() => route.params.target, () => { watch(() => route.params.target, () => {
@ -76,10 +68,6 @@ watch(() => route.params.target, () => {
<Suspense> <Suspense>
<LoadModels :source="source" :loaded="set_model_load_status" :loaded_pan="loadedPan" /> <LoadModels :source="source" :loaded="set_model_load_status" :loaded_pan="loadedPan" />
</Suspense> </Suspense>
<TresMesh cast-shadow>
<TresBoxGeometry :args="[1, 1, 1]" />
<TresMeshStandardMaterial />
</TresMesh>
<TresMesh :position-y="0" :rotate-x="-Math.PI / 2" receive-shadow> <TresMesh :position-y="0" :rotate-x="-Math.PI / 2" receive-shadow>
<TresPlaneGeometry :args="[200, 200]" /> <TresPlaneGeometry :args="[200, 200]" />
<TresShadowMaterial :opacity="0.2" /> <TresShadowMaterial :opacity="0.2" />

View File

@ -7,7 +7,7 @@ import { usePromoSidebar } from '../../stores/promo_sidebar';
const sidebar = usePromoSidebar() const sidebar = usePromoSidebar()
const sidebar_obj = ref() const sidebar_obj = ref()
onClickOutside(sidebar_obj, () => sidebar.close()) // onClickOutside(sidebar_obj, () => sidebar.close())
</script> </script>
<template> <template>
<div class="sidebar" :class="[{ 'open': sidebar.is_open }]" ref="sidebar_obj"> <div class="sidebar" :class="[{ 'open': sidebar.is_open }]" ref="sidebar_obj">
@ -19,9 +19,10 @@ onClickOutside(sidebar_obj, () => sidebar.close())
<template v-for="p in sidebar.description.split('\n')"> <template v-for="p in sidebar.description.split('\n')">
<p>{{ p }}</p> <p>{{ p }}</p>
</template> </template>
<RouterLink :to="`/promo/main/${sidebar.target}`" v-if="sidebar.target"> <RouterLink class="btn" :to="`/promo/main/${sidebar.target}`" v-if="sidebar.target">
{{ sidebar.target_name }} {{ sidebar.target_name }}
</RouterLink> </RouterLink>
</template> </template>
</div> </div>
</template> </template>
@ -59,5 +60,35 @@ onClickOutside(sidebar_obj, () => sidebar.close())
margin: 1rem 0; margin: 1rem 0;
font-size: 1.25rem; font-size: 1.25rem;
} }
.btn {
color: white;
transition: .2s linear;
background: #0B63F6;
padding-top: 5px;
padding-bottom: 7px;
display: block;
text-align: center;
text-decoration: none;
min-width: 300px;
min-height: 60px;
display: inline-flex;
font-family: 'Nunito', sans-serif;
font-size: 22px;
align-items: center;
justify-content: center;
text-transform: uppercase;
letter-spacing: 1.3px;
font-weight: 700;
color: #313133;
background: #4FD1C5;
background: linear-gradient(90deg, rgba(129, 230, 217, 1) 0%, rgba(79, 209, 197, 1) 100%);
border-radius: 1000px;
box-shadow: 12px 12px 24px rgba(79, 209, 197, .64);
}
.btn:hover {
box-shadow: 0 0 0 2px white, 0 0 0 4px #3C82F8;
}
} }
</style> </style>

View File

@ -37,6 +37,7 @@ interface clickableAreaType {
object_name: string; object_name: string;
source: number; source: number;
target: number; target: number;
target_name?: string
} }
interface PromoSidebarData { interface PromoSidebarData {
title: string title: string

View File

@ -20,8 +20,10 @@ export const usePromoSidebar = defineStore('promo_sidebar', {
this.$state = Object.assign(this.$state, data) this.$state = Object.assign(this.$state, data)
}, },
close() { close() {
this.$reset() if (this.is_open) {
this.is_open = false this.$reset()
this.is_open = false
}
} }
} }
}) })