Merge branch 'bx-906-front'
This commit is contained in:
commit
48e4d91431
|
@ -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": {}
|
||||||
},
|
},
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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,38 +109,14 @@ 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)
|
||||||
})
|
|
||||||
onUnmounted(() => {
|
|
||||||
document.removeEventListener('click', clickEvent)
|
|
||||||
})
|
|
||||||
const pointer = reactive({ x: 0, y: 0 })
|
|
||||||
const clickEvent = (event: MouseEvent) => {
|
|
||||||
console.time('raycaster')
|
|
||||||
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 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
|
if (!target) return
|
||||||
const sidebar_data = {
|
const sidebar_data = {
|
||||||
title: target.name,
|
title: target.name,
|
||||||
|
@ -123,11 +126,11 @@ const clickEvent = (event: MouseEvent) => {
|
||||||
sidebar_data.target = target.target.toString()
|
sidebar_data.target = target.target.toString()
|
||||||
sidebar_data.target_name = target.target_name
|
sidebar_data.target_name = target.target_name
|
||||||
}
|
}
|
||||||
sidebar.open()
|
|
||||||
sidebar.setData(sidebar_data)
|
sidebar.setData(sidebar_data)
|
||||||
}
|
sidebar.open()
|
||||||
console.timeEnd('raycaster')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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>
|
|
@ -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" />
|
||||||
|
|
|
@ -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>
|
|
@ -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
|
||||||
|
|
|
@ -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() {
|
||||||
|
if (this.is_open) {
|
||||||
this.$reset()
|
this.$reset()
|
||||||
this.is_open = false
|
this.is_open = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue