bx-1316-refactoring #14
File diff suppressed because it is too large
Load Diff
|
@ -1,5 +1,5 @@
|
||||||
# Use the official Python image with version 3.11
|
# Use the official Python image with version 3.11
|
||||||
FROM python:3.11
|
FROM ci.svs-tech.pro/library/python:3.11
|
||||||
|
|
||||||
# Set environment variables for Python and unbuffered mode
|
# Set environment variables for Python and unbuffered mode
|
||||||
ENV PYTHONUNBUFFERED 1
|
ENV PYTHONUNBUFFERED 1
|
||||||
|
@ -12,7 +12,7 @@ ENV APP_HOME=/app
|
||||||
WORKDIR $APP_HOME
|
WORKDIR $APP_HOME
|
||||||
# Install deb pkgs for usb
|
# Install deb pkgs for usb
|
||||||
RUN apt-get update
|
RUN apt-get update
|
||||||
RUN apt-get install ffmpeg libsm6 libxext6 -y
|
# RUN apt-get install ffmpeg libsm6 libxext6 -y
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
COPY requirements.txt /app/
|
COPY requirements.txt /app/
|
||||||
RUN pip install --upgrade pip && pip install -r requirements.txt
|
RUN pip install --upgrade pip && pip install -r requirements.txt
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
from django.contrib import admin
|
|
||||||
from .models import Product, Floorplan
|
|
||||||
|
|
||||||
# Register your models here.
|
|
||||||
admin.site.register(Product)
|
|
||||||
admin.site.register(Floorplan)
|
|
|
@ -1,6 +0,0 @@
|
||||||
from django.apps import AppConfig
|
|
||||||
|
|
||||||
|
|
||||||
class ApiConfig(AppConfig):
|
|
||||||
default_auto_field = 'django.db.models.BigAutoField'
|
|
||||||
name = 'api'
|
|
|
@ -1,31 +0,0 @@
|
||||||
from django.db import models
|
|
||||||
import json
|
|
||||||
|
|
||||||
|
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
|
|
||||||
# Create your models here.
|
|
||||||
class Product(models.Model):
|
|
||||||
title = models.CharField(max_length=100)
|
|
||||||
description = models.TextField(default=None, null=True)
|
|
||||||
model3d = models.FileField(default=None, blank=True, null=True, upload_to="files")
|
|
||||||
image1 = models.ImageField(default=None, blank=True, null=True, upload_to="files")
|
|
||||||
image2 = models.ImageField(default=None, blank=True, null=True, upload_to="files")
|
|
||||||
image3 = models.ImageField(default=None, blank=True, null=True, upload_to="files")
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.title
|
|
||||||
|
|
||||||
|
|
||||||
class Floorplan(models.Model):
|
|
||||||
title = models.CharField(max_length=200)
|
|
||||||
np_field = models.TextField()
|
|
||||||
d_size = models.IntegerField(null=True, blank=True)
|
|
||||||
d_border = models.IntegerField(null=True, blank=True)
|
|
||||||
paths = models.TextField()
|
|
||||||
|
|
||||||
|
|
||||||
class FloorplanPoints(models.Model):
|
|
||||||
plan = models.OneToOneField(Floorplan, on_delete=models.CASCADE, primary_key=True)
|
|
||||||
points = models.JSONField()
|
|
|
@ -1,50 +0,0 @@
|
||||||
from rest_framework import routers, serializers, viewsets
|
|
||||||
from .models import Floorplan, FloorplanPoints, Product
|
|
||||||
import logging
|
|
||||||
|
|
||||||
logger = logging.getLogger("root")
|
|
||||||
|
|
||||||
|
|
||||||
class ProductSerializer(serializers.HyperlinkedModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = Product
|
|
||||||
fields = [
|
|
||||||
"id",
|
|
||||||
"title",
|
|
||||||
"description",
|
|
||||||
"model3d",
|
|
||||||
"image1",
|
|
||||||
"image2",
|
|
||||||
"image3",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class FloorplanSerializer(serializers.ModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = Floorplan
|
|
||||||
fields = [
|
|
||||||
"id",
|
|
||||||
"title",
|
|
||||||
"np_field",
|
|
||||||
"d_border",
|
|
||||||
"d_size",
|
|
||||||
"paths",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class FloorplanListSerializer(serializers.ModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = Floorplan
|
|
||||||
fields = [
|
|
||||||
"id",
|
|
||||||
"title",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class FloorplanPointsSerializer(serializers.ModelSerializer):
|
|
||||||
class Meta:
|
|
||||||
model = FloorplanPoints
|
|
||||||
fields = [
|
|
||||||
"points",
|
|
||||||
"plan",
|
|
||||||
]
|
|
|
@ -1,3 +0,0 @@
|
||||||
from django.test import TestCase
|
|
||||||
|
|
||||||
# Create your tests here.
|
|
|
@ -1,66 +0,0 @@
|
||||||
import io
|
|
||||||
import base64
|
|
||||||
import cv2
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
def numpy_arr_to_zip_str(arr):
|
|
||||||
f = io.BytesIO()
|
|
||||||
np.savez_compressed(f, arr=arr)
|
|
||||||
return base64.b64encode(f.getvalue())
|
|
||||||
|
|
||||||
def numpy_zip_str_to_arr(zip_str):
|
|
||||||
f = io.BytesIO(base64.b64decode(zip_str))
|
|
||||||
return np.load(f)['arr'].tolist()
|
|
||||||
|
|
||||||
def read_image(content: bytes) -> np.ndarray:
|
|
||||||
"""
|
|
||||||
Image bytes to OpenCV image
|
|
||||||
|
|
||||||
:param content: Image bytes
|
|
||||||
:returns OpenCV image
|
|
||||||
:raises TypeError: If content is not bytes
|
|
||||||
:raises ValueError: If content does not represent an image
|
|
||||||
"""
|
|
||||||
if not isinstance(content, bytes):
|
|
||||||
raise TypeError(f"Expected 'content' to be bytes, received: {type(content)}")
|
|
||||||
image = cv2.imdecode(np.frombuffer(content, dtype=np.uint8), cv2.IMREAD_COLOR)
|
|
||||||
if image is None:
|
|
||||||
raise ValueError(f"Expected 'content' to be image bytes")
|
|
||||||
return image
|
|
||||||
|
|
||||||
|
|
||||||
def parse_image(img):
|
|
||||||
(img_h, img_w) = img.shape[:2]
|
|
||||||
t = 1920
|
|
||||||
w = t
|
|
||||||
h = int((img_h / img_w) * t)
|
|
||||||
img = cv2.resize(img, (w, h))
|
|
||||||
|
|
||||||
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
|
|
||||||
gray = 255 - gray
|
|
||||||
gray = cv2.threshold(gray, 250, 255, cv2.THRESH_BINARY)[1]
|
|
||||||
gray = cv2.blur(gray, (10, 5))
|
|
||||||
contours, hierarchy = cv2.findContours(gray, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)
|
|
||||||
|
|
||||||
for cnt in contours:
|
|
||||||
area = cv2.contourArea(cnt)
|
|
||||||
# if area > 150000 and area < 500000:
|
|
||||||
cv2.drawContours(img, [cnt], 0, (255, 0, 0), 2)
|
|
||||||
|
|
||||||
svg_paths = []
|
|
||||||
|
|
||||||
for cnt in contours:
|
|
||||||
if len(cnt) > 80:
|
|
||||||
svg_path = "M"
|
|
||||||
for i in range(len(cnt)):
|
|
||||||
x, y = cnt[i][0]
|
|
||||||
svg_path += f"{x} {y} "
|
|
||||||
svg_paths.append(svg_path)
|
|
||||||
|
|
||||||
return {
|
|
||||||
"width": w,
|
|
||||||
"height": h,
|
|
||||||
"paths": svg_paths,
|
|
||||||
"array": gray.tolist(),
|
|
||||||
"b64": numpy_arr_to_zip_str(gray),
|
|
||||||
}
|
|
|
@ -1,134 +0,0 @@
|
||||||
import json
|
|
||||||
from rest_framework.parsers import JSONParser, MultiPartParser
|
|
||||||
from rest_framework.views import APIView
|
|
||||||
from rest_framework import status
|
|
||||||
|
|
||||||
from django.http import JsonResponse
|
|
||||||
from django.shortcuts import get_object_or_404
|
|
||||||
|
|
||||||
from api.tracer import parse_image, read_image, numpy_zip_str_to_arr
|
|
||||||
|
|
||||||
from .serializers import (
|
|
||||||
FloorplanListSerializer,
|
|
||||||
FloorplanPointsSerializer,
|
|
||||||
FloorplanSerializer,
|
|
||||||
ProductSerializer,
|
|
||||||
)
|
|
||||||
from .models import Floorplan, FloorplanPoints, Product
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
logger = logging.getLogger("root")
|
|
||||||
|
|
||||||
|
|
||||||
class Products(APIView):
|
|
||||||
def get(self, request):
|
|
||||||
tasks = Product.objects.all()
|
|
||||||
serializer = ProductSerializer(tasks, many=True)
|
|
||||||
return JsonResponse(serializer.data, safe=False)
|
|
||||||
|
|
||||||
def post(self, request):
|
|
||||||
data = JSONParser().parse(request)
|
|
||||||
serializer = ProductSerializer(data=data)
|
|
||||||
if serializer.is_valid():
|
|
||||||
serializer.save()
|
|
||||||
return JsonResponse(serializer.data, status=201)
|
|
||||||
|
|
||||||
return JsonResponse(serializer.errors, status=400)
|
|
||||||
|
|
||||||
|
|
||||||
class FloorplanView(APIView):
|
|
||||||
|
|
||||||
parser_classes = (
|
|
||||||
MultiPartParser,
|
|
||||||
JSONParser,
|
|
||||||
)
|
|
||||||
|
|
||||||
def post(self, request):
|
|
||||||
try:
|
|
||||||
file = request.FILES["demo"]
|
|
||||||
logger.info(file.__dict__)
|
|
||||||
res = parse_image(read_image(file.read()))
|
|
||||||
serializer = FloorplanSerializer(
|
|
||||||
data={
|
|
||||||
"title": file.name,
|
|
||||||
"np_field": res["b64"].decode(),
|
|
||||||
"paths": json.dumps(res["paths"]),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if serializer.is_valid():
|
|
||||||
serializer.save()
|
|
||||||
return JsonResponse(
|
|
||||||
data={
|
|
||||||
"response": {
|
|
||||||
"np_field": res["array"],
|
|
||||||
"paths": res["paths"],
|
|
||||||
}
|
|
||||||
},
|
|
||||||
status=201,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
logger.info(serializer.errors)
|
|
||||||
return JsonResponse(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(e)
|
|
||||||
raise e
|
|
||||||
|
|
||||||
def get(self, request, id=None):
|
|
||||||
try:
|
|
||||||
if id is not None:
|
|
||||||
item = Floorplan.objects.get(id=id)
|
|
||||||
serializer = FloorplanSerializer(item, many=False)
|
|
||||||
data = serializer.data
|
|
||||||
data["np_field"] = numpy_zip_str_to_arr(data["np_field"])
|
|
||||||
return JsonResponse(data, safe=False)
|
|
||||||
else:
|
|
||||||
items = Floorplan.objects.only("id", "title").all()
|
|
||||||
serializer = FloorplanListSerializer(items, many=True)
|
|
||||||
return JsonResponse(serializer.data, safe=False)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(e)
|
|
||||||
raise e
|
|
||||||
|
|
||||||
def patch(self, request, id):
|
|
||||||
try:
|
|
||||||
if id is not None:
|
|
||||||
item = Floorplan.objects.get(id=id)
|
|
||||||
serializer = FloorplanSerializer(item, data=request.data, partial=True)
|
|
||||||
if serializer.is_valid():
|
|
||||||
serializer.save()
|
|
||||||
return JsonResponse(serializer.data, status=status.HTTP_200_OK)
|
|
||||||
else:
|
|
||||||
logger.info(serializer.errors)
|
|
||||||
return JsonResponse(
|
|
||||||
serializer.errors, status=status.HTTP_400_BAD_REQUEST
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
return JsonResponse("No item", status=status.HTTP_400_BAD_REQUEST)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(e)
|
|
||||||
raise e
|
|
||||||
|
|
||||||
|
|
||||||
class FloorplanPointsView(APIView):
|
|
||||||
def get(self, request, id):
|
|
||||||
if FloorplanPoints.objects.filter(plan=id).exists():
|
|
||||||
points = FloorplanPoints.objects.get(plan=id)
|
|
||||||
serializer = FloorplanPointsSerializer(points, many=False)
|
|
||||||
return JsonResponse(serializer.data, safe=False)
|
|
||||||
else:
|
|
||||||
return JsonResponse(
|
|
||||||
"No item", safe=False, status=status.HTTP_400_BAD_REQUEST
|
|
||||||
)
|
|
||||||
|
|
||||||
def post(self, request, id):
|
|
||||||
data = JSONParser().parse(request)
|
|
||||||
data["plan"] = id
|
|
||||||
floorplapoints_object = get_object_or_404(FloorplanPoints, plan=id)
|
|
||||||
serializer = FloorplanPointsSerializer(floorplapoints_object, data=data, partial=True)
|
|
||||||
if serializer.is_valid():
|
|
||||||
serializer.save()
|
|
||||||
return JsonResponse(serializer.data, status=status.HTTP_201_CREATED)
|
|
||||||
else:
|
|
||||||
return JsonResponse(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
|
|
@ -84,7 +84,6 @@ INSTALLED_APPS = [
|
||||||
"crispy_forms",
|
"crispy_forms",
|
||||||
"crispy_bootstrap4",
|
"crispy_bootstrap4",
|
||||||
"colorfield",
|
"colorfield",
|
||||||
"api",
|
|
||||||
"frontImages",
|
"frontImages",
|
||||||
"object",
|
"object",
|
||||||
]
|
]
|
||||||
|
|
|
@ -18,7 +18,6 @@ from django.contrib import admin
|
||||||
from django.urls import include, path
|
from django.urls import include, path
|
||||||
from django.conf.urls.static import static
|
from django.conf.urls.static import static
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
from api import views
|
|
||||||
from rest_framework import routers
|
from rest_framework import routers
|
||||||
|
|
||||||
from frontImages import views as frontimg_views
|
from frontImages import views as frontimg_views
|
||||||
|
@ -33,8 +32,4 @@ router.register(r'api/obj/clickable', object_views.ClickableAreaViewSet)
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', include(router.urls)),
|
path('', include(router.urls)),
|
||||||
path("admin/", admin.site.urls),
|
path("admin/", admin.site.urls),
|
||||||
path("api/products", csrf_exempt(views.Products.as_view())),
|
|
||||||
path("api/floorplan/", csrf_exempt(views.FloorplanView.as_view())),
|
|
||||||
path("api/floorplan/<str:id>", csrf_exempt(views.FloorplanView.as_view())),
|
|
||||||
path("api/floorplan/<str:id>/points", csrf_exempt(views.FloorplanPointsView.as_view())),
|
|
||||||
] + static('/files', document_root='files')
|
] + static('/files', document_root='files')
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
FROM node:21
|
FROM ci.svs-tech.pro/library/node:21
|
||||||
|
|
||||||
RUN mkdir -p /src
|
RUN mkdir -p /src
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ declare module 'vue' {
|
||||||
IMdiPagePreviousOutline: typeof import('~icons/mdi/page-previous-outline')['default']
|
IMdiPagePreviousOutline: typeof import('~icons/mdi/page-previous-outline')['default']
|
||||||
IMdiShop: typeof import('~icons/mdi/shop')['default']
|
IMdiShop: typeof import('~icons/mdi/shop')['default']
|
||||||
IMdiVideo3d: typeof import('~icons/mdi/video3d')['default']
|
IMdiVideo3d: typeof import('~icons/mdi/video3d')['default']
|
||||||
Item: typeof import('./src/components/Floorplan/item.vue')['default']
|
Item: typeof import('./src/components/Promo/item.vue')['default']
|
||||||
Load_models: typeof import('./src/components/Promo/load_models.vue')['default']
|
Load_models: typeof import('./src/components/Promo/load_models.vue')['default']
|
||||||
Main: typeof import('./src/components/Promo/main.vue')['default']
|
Main: typeof import('./src/components/Promo/main.vue')['default']
|
||||||
ModelItem: typeof import('./src/components/Promo/modelItem.vue')['default']
|
ModelItem: typeof import('./src/components/Promo/modelItem.vue')['default']
|
||||||
|
|
|
@ -1,17 +1,4 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import { RouterLink } from 'vue-router';
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="nav">
|
|
||||||
<RouterLink to="/projects"><i-mdi-shop /></RouterLink>
|
|
||||||
<RouterLink to="/game"><i-mdi-hexagon-outline /></RouterLink>
|
|
||||||
<RouterLink to="/promo"><i-mdi-monitor-screenshot /></RouterLink>
|
|
||||||
<span style="flex-grow:1"></span>
|
|
||||||
<RouterLink to="/"><i-mdi-home /></RouterLink>
|
|
||||||
</div>
|
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<RouterView />
|
<RouterView />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
<style lang="scss" scoped></style>
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { onMounted, } from 'vue';
|
|
||||||
import { useFloorplanStore } from '../../stores/floorplan';
|
|
||||||
|
|
||||||
const floorplan = useFloorplanStore()
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
await floorplan.getList()
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
<template>
|
|
||||||
<div class="container">
|
|
||||||
<ul>
|
|
||||||
<li v-for="item in floorplan.items">
|
|
||||||
<RouterLink :to="`/floorplan/${item.id}`">
|
|
||||||
{{ item.title }}
|
|
||||||
</RouterLink>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
|
@ -1,150 +0,0 @@
|
||||||
<style lang="scss" scoped></style>
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { onMounted, ref } from 'vue';
|
|
||||||
import PF, { Grid } from 'pathfinding'
|
|
||||||
|
|
||||||
import { useFloorplanStore } from '../../stores/floorplan';
|
|
||||||
import { useRoute } from 'vue-router';
|
|
||||||
import { random_сolor } from '../../helpers';
|
|
||||||
|
|
||||||
type PathItem = { path: string, unwalkable: boolean, x: number, y: number }
|
|
||||||
const floorplan = useFloorplanStore()
|
|
||||||
|
|
||||||
const canvasElement = ref();
|
|
||||||
const context = ref();
|
|
||||||
|
|
||||||
const grid = ref<Grid>()
|
|
||||||
const startPoint = ref<{ x: number, y: number }>({ x: 25, y: 40 })
|
|
||||||
const endPoint = ref<{ x: number, y: number }>()
|
|
||||||
const startToEndPath = ref<number[][] | undefined>([])
|
|
||||||
|
|
||||||
const plan = useFloorplanStore()
|
|
||||||
const paths = ref<PathItem[]>([])
|
|
||||||
|
|
||||||
const finder = new PF.BreadthFirstFinder();
|
|
||||||
|
|
||||||
const nextFrame = () => new Promise(resolve => requestAnimationFrame(resolve));
|
|
||||||
const route = useRoute()
|
|
||||||
|
|
||||||
const newDraw = async () => {
|
|
||||||
endPoint.value = undefined
|
|
||||||
startToEndPath.value = undefined
|
|
||||||
|
|
||||||
context.value = canvasElement.value?.getContext('2d') || undefined;
|
|
||||||
const lines = plan.np_array
|
|
||||||
const c = random_сolor()
|
|
||||||
for (let indexY = 0; indexY < lines.length; indexY++) {
|
|
||||||
const line = lines[indexY];
|
|
||||||
for (let indexX = 0; indexX < line.length; indexX++) {
|
|
||||||
const point = line[indexX];
|
|
||||||
if (canvasElement.value && context.value && point > 0) {
|
|
||||||
context.value.fillStyle = c.replace(')', ',0.5)').replace('rgb', 'rgba')
|
|
||||||
context.value.fillRect(indexX, indexY, 1, 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (indexY % 10 == 0) {
|
|
||||||
await nextFrame()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const quantum_lines = plan.prepared_array
|
|
||||||
const chunkSize = plan.chunk_size || 8
|
|
||||||
quantum_lines.forEach((line, indexY) => {
|
|
||||||
const targetY = indexY * chunkSize
|
|
||||||
line.forEach((point, indexX) => {
|
|
||||||
const targetX = indexX * chunkSize
|
|
||||||
paths.value.push({
|
|
||||||
path: `M${targetX} ${targetY} ${targetX + chunkSize} ${targetY} ${targetX + chunkSize} ${targetY + chunkSize} ${targetX} ${targetY + chunkSize}Z`,
|
|
||||||
unwalkable: !!point,
|
|
||||||
x: indexX,
|
|
||||||
y: indexY,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
grid.value = new PF.Grid(plan.prepared_array.map(y => y.map(x => x > 0 ? 1 : 0)))
|
|
||||||
}
|
|
||||||
|
|
||||||
const findPath = async () => {
|
|
||||||
// filders.forEach((finder,i) => {
|
|
||||||
console.time(`findpath`)
|
|
||||||
if (!endPoint.value) return
|
|
||||||
const localPath: number[][] = finder.findPath(
|
|
||||||
Math.round(startPoint.value.x),
|
|
||||||
Math.round(startPoint.value.y),
|
|
||||||
Math.round(endPoint.value.x),
|
|
||||||
Math.round(endPoint.value.y),
|
|
||||||
(grid.value?.clone() as Grid)
|
|
||||||
);
|
|
||||||
startToEndPath.value = localPath
|
|
||||||
console.timeEnd(`findpath`)
|
|
||||||
// });
|
|
||||||
}
|
|
||||||
|
|
||||||
const setPointSvg = (item: { x: number, y: number }) => {
|
|
||||||
// startToEndPath.value = []
|
|
||||||
const startP = floorplan.points.find(el => el.type === 'start')
|
|
||||||
if (!startP) return
|
|
||||||
startPoint.value = { x: startP.points.x, y: startP.points.y }
|
|
||||||
endPoint.value = { x: item.x, y: item.y }
|
|
||||||
findPath()
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
await floorplan.getData(parseInt(route.params.id as string))
|
|
||||||
newDraw()
|
|
||||||
})
|
|
||||||
const cw = 1920
|
|
||||||
const ch = 800
|
|
||||||
</script>
|
|
||||||
<template>
|
|
||||||
<div class="container" style="display: flex; justify-content: center; align-items: center; flex-direction: column;">
|
|
||||||
<div class="svg-container" style="position: relative">
|
|
||||||
<canvas ref="canvasElement" :width="cw" :height="ch"></canvas>
|
|
||||||
<svg ref="svgElement" :width="cw" :height="ch" style="position: absolute; top: 0; right: 0;">
|
|
||||||
<path
|
|
||||||
v-for="item in paths.filter(item => floorplan.points.find(el => el.points.x == item.x && el.points.y == item.y))"
|
|
||||||
:d="item.path" @click="setPointSvg(item)" :class="[
|
|
||||||
{ 'endPoint': !!(floorplan.points.find(el => el.type == 'start' && el.points.x == item.x && el.points.y == item.y)) },
|
|
||||||
{ 'startPoint': !!(floorplan.points.find(el => el.type.indexOf('cabinet') !== -1 && el.points.x === item.x && el.points.y === item.y)) },
|
|
||||||
{ 'pathPoint': (startToEndPath && startToEndPath.find((el: number[]) => el[0] == item.x && el[1] == item.y)) },
|
|
||||||
]">
|
|
||||||
</path>
|
|
||||||
<path
|
|
||||||
v-for="item in paths.filter(item => (startToEndPath || []).find(el => el[0] == item.x && el[1] == item.y))"
|
|
||||||
:d="item.path" @click="setPointSvg(item)" :class="[
|
|
||||||
{ 'endPoint': !!(floorplan.points.find(el => el.type == 'start' && el.points.x == item.x && el.points.y == item.y)) },
|
|
||||||
{ 'startPoint': !!(floorplan.points.find(el => el.type.indexOf('cabinet') !== -1 && el.points.x === item.x && el.points.y === item.y)) },
|
|
||||||
{ 'pathPoint': (startToEndPath && startToEndPath.find((el: number[]) => el[0] == item.x && el[1] == item.y)) },
|
|
||||||
]">
|
|
||||||
</path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<div class="buttons">
|
|
||||||
<template v-for="item in floorplan.points">
|
|
||||||
<span v-if="item.type !== 'start'" @click="setPointSvg({ x: item.points.x, y: item.points.y })">
|
|
||||||
{{ item.title }}
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<style scoped>
|
|
||||||
svg path.endPoint {
|
|
||||||
fill: blue;
|
|
||||||
}
|
|
||||||
|
|
||||||
svg path.startPoint {
|
|
||||||
fill: lawngreen;
|
|
||||||
}
|
|
||||||
|
|
||||||
svg path.pathPoint {
|
|
||||||
fill: gold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.buttons {
|
|
||||||
display: flex;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,151 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref } from 'vue';
|
|
||||||
import { UseDraggable } from '@vueuse/components';
|
|
||||||
import { type Position } from '@vueuse/core';
|
|
||||||
import { random_сolor, shuffle_array, getRandomIntInclusive } from '../helpers';
|
|
||||||
|
|
||||||
import icon1 from '~icons/mdi/square';
|
|
||||||
import icon1_o from '~icons/mdi/square-outline';
|
|
||||||
import icon2 from '~icons/mdi/triangle';
|
|
||||||
import icon2_o from '~icons/mdi/triangle-outline';
|
|
||||||
import icon3 from '~icons/mdi/circle';
|
|
||||||
import icon3_o from '~icons/mdi/circle-outline';
|
|
||||||
import icon4 from '~icons/mdi/hexagon';
|
|
||||||
import icon4_o from '~icons/mdi/hexagon-outline';
|
|
||||||
const icons_list = [
|
|
||||||
icon1,
|
|
||||||
icon1_o,
|
|
||||||
icon2,
|
|
||||||
icon2_o,
|
|
||||||
icon3,
|
|
||||||
icon3_o,
|
|
||||||
icon4,
|
|
||||||
icon4_o,
|
|
||||||
]
|
|
||||||
const colors = ref([
|
|
||||||
random_сolor(),
|
|
||||||
random_сolor(),
|
|
||||||
random_сolor(),
|
|
||||||
random_сolor(),
|
|
||||||
])
|
|
||||||
const icons = [
|
|
||||||
{
|
|
||||||
id: 1, fill: 0, outline: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2, fill: 2, outline: 3,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3, fill: 4, outline: 5,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4, fill: 6, outline: 7,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
const source_icons = ref(shuffle_array(icons))
|
|
||||||
const target_icons = ref(shuffle_array(icons))
|
|
||||||
|
|
||||||
const active_drag = ref<string | null>(null)
|
|
||||||
const dragStart = (_: Position, event: PointerEvent) => {
|
|
||||||
const element = event.currentTarget
|
|
||||||
if (element instanceof HTMLElement && element.dataset.id) {
|
|
||||||
active_drag.value = element.dataset.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const dragEnd = (position: Position, _: PointerEvent) => {
|
|
||||||
const target = document.elementsFromPoint(position.x, position.y).find(el => el.className == 'target-item')
|
|
||||||
if (target instanceof HTMLElement && target.dataset.id) {
|
|
||||||
const targetId = target.dataset.id
|
|
||||||
const sourceId = active_drag.value
|
|
||||||
if (targetId == sourceId) {
|
|
||||||
success.value.push(parseInt(targetId))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const componentKey = ref(0)
|
|
||||||
const success = ref<number[]>([])
|
|
||||||
|
|
||||||
const updateGame = () => {
|
|
||||||
success.value = []
|
|
||||||
componentKey.value += 1
|
|
||||||
source_icons.value = shuffle_array(icons)
|
|
||||||
target_icons.value = shuffle_array(icons)
|
|
||||||
colors.value = [random_сolor(), random_сolor(), random_сolor(), random_сolor()]
|
|
||||||
}
|
|
||||||
|
|
||||||
const coor = (n: number) => {
|
|
||||||
let x = 0
|
|
||||||
let y = 0
|
|
||||||
|
|
||||||
const groups = [
|
|
||||||
{ x: [100, 1400], y: [100, 300] },
|
|
||||||
{ x: [1400, 1600], y: [100, 800] },
|
|
||||||
{ x: [100, 1600], y: [700, 800] },
|
|
||||||
{ x: [100, 200], y: [100, 800] },
|
|
||||||
]
|
|
||||||
const d = groups[n]
|
|
||||||
x = getRandomIntInclusive(d.x[0], d.x[1])
|
|
||||||
y = getRandomIntInclusive(d.y[0], d.y[1])
|
|
||||||
return { x, y }
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<template>
|
|
||||||
<div class="game" :key="componentKey">
|
|
||||||
<div class="target">
|
|
||||||
<span class="target-item" v-for="item in target_icons" :data-id="item.id">
|
|
||||||
<component :is="success.includes(item.id) ? icons_list[item.fill] : icons_list[item.outline]"
|
|
||||||
:style="{ 'color': colors[item.id - 1] }"></component>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<UseDraggable v-for="item in source_icons" class="source-item" :data-id="item.id"
|
|
||||||
:initial-value="coor(getRandomIntInclusive(1, 3))" :on-start="dragStart" :on-end="dragEnd">
|
|
||||||
<component v-if="!success.includes(item.id)" :is="icons_list[item.fill]"
|
|
||||||
:style="{ 'color': colors[item.id - 1] }" :ref="item.ref"></component>
|
|
||||||
</UseDraggable>
|
|
||||||
<div class="links" v-if="success.length >= icons.length">
|
|
||||||
<RouterLink to="/">На главную</RouterLink>
|
|
||||||
<RouterLink to="/game" @click="updateGame">Еще раз</RouterLink>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<style scoped lans="scss">
|
|
||||||
.game {
|
|
||||||
position: fixed;
|
|
||||||
height: 100vh;
|
|
||||||
width: 100vw;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-around;
|
|
||||||
}
|
|
||||||
|
|
||||||
.target {
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.target-item {
|
|
||||||
font-size: 12rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.source-item {
|
|
||||||
font-size: 7rem;
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
.links {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
font-size: 5rem;
|
|
||||||
align-items: center;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.links a {
|
|
||||||
text-decoration: none;
|
|
||||||
color: black;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -9,21 +9,9 @@
|
||||||
<div class="sidebar"></div>
|
<div class="sidebar"></div>
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
|
||||||
<RouterLink to="projects">Проекты</RouterLink>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<RouterLink to="game">Игра</RouterLink>
|
|
||||||
</li>
|
|
||||||
<li>
|
<li>
|
||||||
<RouterLink to="promo">Промо</RouterLink>
|
<RouterLink to="promo">Промо</RouterLink>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
|
||||||
<a href="https://timesheet.kustarshina.ru/">Табель рабочего времени</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="http://zoo.svs-tech.pro/">Билетная система зоопарка</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,137 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import { onMounted, reactive, ref } from 'vue';
|
|
||||||
import { ModelFbx } from 'vue-3d-model';
|
|
||||||
|
|
||||||
import type { UseSwipeDirection } from '@vueuse/core';
|
|
||||||
import { useSwipe } from '@vueuse/core';
|
|
||||||
|
|
||||||
import Fireworks from '@fireworks-js/vue';
|
|
||||||
import type { FireworksOptions } from '@fireworks-js/vue'
|
|
||||||
|
|
||||||
import RandomIcon from '../components/RandomIcon.vue';
|
|
||||||
import { useProductStore } from '../stores/product';
|
|
||||||
import type { ProductInfo } from '../stores/product';
|
|
||||||
|
|
||||||
const IMAGE_URL = import.meta.env.VITE_IMAGE_URL ?? window.location.origin
|
|
||||||
|
|
||||||
type StateType = {
|
|
||||||
active_product?: ProductInfo
|
|
||||||
show_model: boolean,
|
|
||||||
show_fireworks: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
const products = useProductStore()
|
|
||||||
|
|
||||||
const state: StateType = reactive({
|
|
||||||
active_product: undefined,
|
|
||||||
show_model: false,
|
|
||||||
show_fireworks: false
|
|
||||||
})
|
|
||||||
|
|
||||||
const reset = () => {
|
|
||||||
state.active_product = undefined
|
|
||||||
state.show_model = false
|
|
||||||
state.show_fireworks = false
|
|
||||||
}
|
|
||||||
|
|
||||||
const setActive = (id: number) => {
|
|
||||||
state.active_product = products.list.find(el => el.id == id)
|
|
||||||
state.show_model = false
|
|
||||||
state.show_fireworks = false
|
|
||||||
}
|
|
||||||
|
|
||||||
const toggleShowCanvas = () => {
|
|
||||||
state.show_model = !state.show_model
|
|
||||||
}
|
|
||||||
|
|
||||||
const target = ref<HTMLElement | null>(null)
|
|
||||||
useSwipe(target, {
|
|
||||||
onSwipeEnd(_: TouchEvent, direction: UseSwipeDirection) {
|
|
||||||
if (state.show_model) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const index = products.list.findIndex(el => el.id == state.active_product?.id)
|
|
||||||
if (direction === 'right') {
|
|
||||||
if (index == products.list.length - 1) {
|
|
||||||
setActive(products.list[0].id)
|
|
||||||
} else {
|
|
||||||
setActive(products.list[index + 1].id)
|
|
||||||
}
|
|
||||||
} else if (direction === 'left') {
|
|
||||||
if (index == 0) {
|
|
||||||
setActive(products.list[products.list.length - 1].id)
|
|
||||||
} else {
|
|
||||||
setActive(products.list[index - 1].id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const fw = ref<InstanceType<typeof Fireworks>>()
|
|
||||||
const options = ref<FireworksOptions>({
|
|
||||||
lineStyle: 'square',
|
|
||||||
intensity: 50,
|
|
||||||
lineWidth: {
|
|
||||||
explosion: { min: 7, max: 10 },
|
|
||||||
trace: { min: 7, max: 10 },
|
|
||||||
|
|
||||||
}
|
|
||||||
})
|
|
||||||
onMounted(async () => {
|
|
||||||
products.getData()
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
@import '../assets/projects.scss';
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="container">
|
|
||||||
<div class="sidebar">
|
|
||||||
<ul class="menu">
|
|
||||||
<li v-for="item in products.list">
|
|
||||||
<RandomIcon v-if="item.id === state.active_product?.id" />
|
|
||||||
<a @click.stop.prevent="setActive(item.id)" :href="item.id.toString()">
|
|
||||||
{{ item.title }}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="header"><span class="logo-header" @click="reset">Проекты Кустарщины</span></div>
|
|
||||||
<div class="main product" v-if="state.active_product" ref="target">
|
|
||||||
<div class="product-image" v-if="!state.show_model && state.active_product.image1">
|
|
||||||
<img :style="{
|
|
||||||
clipPath: `polygon(
|
|
||||||
0% 10%, 10% 0%,
|
|
||||||
90% 0%, 100% 10%,
|
|
||||||
100% 90%, 90% 100%,
|
|
||||||
10% 100%, 0% 90%
|
|
||||||
)`}" :src="`${IMAGE_URL}${state.active_product.image1}`" />
|
|
||||||
</div>
|
|
||||||
<div class="product-description" v-if="!state.show_model">
|
|
||||||
{{ state.active_product.description }}
|
|
||||||
</div>
|
|
||||||
<a class="product-model-icon" v-if="state.active_product.model3d" @click.stop.prevent="toggleShowCanvas">
|
|
||||||
<i-mdi-video-3d v-if="!state.show_model" />
|
|
||||||
<i-mdi-file v-else />
|
|
||||||
</a>
|
|
||||||
<model-fbx v-if="state.show_model && state.active_product.model3d" class="product-model"
|
|
||||||
:src="`${IMAGE_URL}/${state.active_product.model3d.replace('/back', '')}`"
|
|
||||||
:backgroundAlpha="0"></model-fbx>
|
|
||||||
</div>
|
|
||||||
<div class="main" v-else>
|
|
||||||
<img class="logo-img" src="../assets/logo_color.png"
|
|
||||||
@click="() => { state.show_fireworks = !state.show_fireworks }" />
|
|
||||||
<Fireworks ref="fw" v-if="state.show_fireworks" :autostart="true" :options="options" :style="{
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
position: 'fixed',
|
|
||||||
zIndex: 0,
|
|
||||||
pointerEvents: 'none',
|
|
||||||
}" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
|
@ -1,13 +1,13 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, onUnmounted, reactive, ref, watch } from 'vue';
|
import { onMounted, onUnmounted, reactive, ref, watch } from 'vue';
|
||||||
import {
|
import {
|
||||||
Box3, CircleGeometry, Color, DoubleSide, Group, Mesh, MeshBasicMaterial,
|
Box3, Color, DoubleSide, Group, Mesh, MeshBasicMaterial,
|
||||||
MeshStandardMaterial,
|
MeshStandardMaterial,
|
||||||
MeshStandardMaterialParameters,
|
MeshStandardMaterialParameters,
|
||||||
PlaneGeometry, SpriteMaterial, TextureLoader, Vector2, Vector3,
|
PlaneGeometry, SpriteMaterial, TextureLoader, Vector2, Vector3,
|
||||||
} from 'three';
|
} from 'three';
|
||||||
|
|
||||||
import { useTresContext, useSeek, useRenderLoop, useTexture, useLoop } from '@tresjs/core';
|
import { useTresContext, useSeek, useTexture, useLoop } from '@tresjs/core';
|
||||||
import { useGLTF } from '@tresjs/cientos'
|
import { useGLTF } from '@tresjs/cientos'
|
||||||
|
|
||||||
import Env from './env.vue'
|
import Env from './env.vue'
|
||||||
|
@ -15,6 +15,7 @@ import Env from './env.vue'
|
||||||
import { IMAGE_URL, PROMOBG, SERVER_URL, } from '../../constants'
|
import { IMAGE_URL, PROMOBG, SERVER_URL, } from '../../constants'
|
||||||
import { usePromoSidebar } from '../../stores/promo_sidebar';
|
import { usePromoSidebar } from '../../stores/promo_sidebar';
|
||||||
import { usePromoScene } from '../../stores/promo_scene';
|
import { usePromoScene } from '../../stores/promo_scene';
|
||||||
|
import { mobileAndTabletCheck } from '../../helpers';
|
||||||
|
|
||||||
const props = defineProps(['source', 'loaded', 'loaded_pan'])
|
const props = defineProps(['source', 'loaded', 'loaded_pan'])
|
||||||
|
|
||||||
|
@ -27,33 +28,33 @@ const sidebar_scene = usePromoScene()
|
||||||
const { controls, camera, scene, raycaster, renderer } = useTresContext()
|
const { controls, camera, scene, raycaster, renderer } = useTresContext()
|
||||||
const { pause, resume } = useLoop()
|
const { pause, resume } = useLoop()
|
||||||
const { seekByName, seekAllByName } = useSeek()
|
const { seekByName, seekAllByName } = useSeek()
|
||||||
const envVars = reactive({}) as {
|
const envVars = reactive({}) as EnvVars
|
||||||
focus: number,
|
|
||||||
hdr_gainmap?: string,
|
|
||||||
hdr_json?: string,
|
|
||||||
hdr_webp?: string,
|
|
||||||
clear_color?: string,
|
|
||||||
env_displacementmap?: string,
|
|
||||||
env_normalmap?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const groundTexture = await useTexture({
|
const groundTexture = await useTexture({
|
||||||
displacementMap: '/ground_displacement.jpg',
|
displacementMap: '/ground_displacement.jpg',
|
||||||
})
|
})
|
||||||
|
|
||||||
const timer = ref(10)
|
const timer = ref(10)
|
||||||
let int: any;
|
setInterval(() => {
|
||||||
|
console.log({ timer: timer.value })
|
||||||
|
if (timer.value > 0) {
|
||||||
|
timer.value -= 1
|
||||||
|
} else if (timer.value == 0 && !(controls.value as any).autoRotate && (controls.value as any).enabled) {
|
||||||
|
pause()
|
||||||
|
if (controls.value) {
|
||||||
|
camera.value?.position.set(10, (controls.value as any).minDistance * 0.75, (controls.value as any).minDistance);
|
||||||
|
camera.value?.lookAt(0, 0, 0);
|
||||||
|
(controls.value as any).autoRotate = true;
|
||||||
|
(controls.value as any).autoRotateSpeed = 1;
|
||||||
|
}
|
||||||
|
resume()
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
// renderer.value.capabilities.maxTextures = 4
|
// renderer.value.capabilities.maxTextures = 4
|
||||||
renderer.value.capabilities.maxTextureSize = 512
|
// renderer.value.capabilities.maxTextureSize = 512
|
||||||
renderer.value.capabilities.precision = 'lowp'
|
renderer.value.capabilities.precision = 'lowp'
|
||||||
|
|
||||||
const mobileAndTabletCheck = () => {
|
|
||||||
let check = false;
|
|
||||||
(function (a) { if (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(a) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0, 4))) check = true; })(navigator.userAgent || navigator.vendor || window.opera);
|
|
||||||
return check;
|
|
||||||
};
|
|
||||||
|
|
||||||
const loadModels = async () => {
|
const loadModels = async () => {
|
||||||
const res = await fetch(`${SERVER_URL}/api/obj/scene/${props.source}`)
|
const res = await fetch(`${SERVER_URL}/api/obj/scene/${props.source}`)
|
||||||
const raw_data = await res.json() as scene3D
|
const raw_data = await res.json() as scene3D
|
||||||
|
@ -74,14 +75,18 @@ const loadModels = async () => {
|
||||||
const data = raw_data.elements
|
const data = raw_data.elements
|
||||||
if (!controls.value) return;
|
if (!controls.value) return;
|
||||||
|
|
||||||
camera.value?.position.set(1, 1, 1);
|
|
||||||
controls.value.enabled = false;
|
controls.value.enabled = false;
|
||||||
// console.log(mobileAndTabletCheck() ? raw_data.min_distance * 0.5 : raw_data.min_distance);
|
|
||||||
(controls.value as any).minDistance = mobileAndTabletCheck() ? raw_data.min_distance * 0.5 : raw_data.min_distance;
|
(controls.value as any).minDistance = mobileAndTabletCheck() ? raw_data.min_distance * 0.5 : raw_data.min_distance;
|
||||||
(controls.value as any).maxDistance = raw_data.max_distance;
|
(controls.value as any).maxDistance = raw_data.max_distance;
|
||||||
(controls.value as any)._needsUpdate = true;
|
(controls.value as any)._needsUpdate = true;
|
||||||
(controls.value as any).update()
|
(controls.value as any).update()
|
||||||
|
|
||||||
|
camera.value?.position.set(10, (controls.value as any).minDistance * 0.75, (controls.value as any).minDistance);
|
||||||
|
camera.value?.lookAt(0, 0, 0);
|
||||||
|
|
||||||
|
(controls.value as any).reset()
|
||||||
|
|
||||||
const sidebar_items = []
|
const sidebar_items = []
|
||||||
clickable_items.value = []
|
clickable_items.value = []
|
||||||
for (let index = 0; index < data.length; index++) {
|
for (let index = 0; index < data.length; index++) {
|
||||||
|
@ -201,23 +206,10 @@ const loadModels = async () => {
|
||||||
controls.value.enabled = true;
|
controls.value.enabled = true;
|
||||||
props.loaded(false)
|
props.loaded(false)
|
||||||
|
|
||||||
clearInterval(int)
|
|
||||||
timer.value = 10
|
timer.value = 10
|
||||||
int = setInterval(() => {
|
if (controls.value && (controls.value as any).autoRotate) {
|
||||||
if (timer.value > 0) {
|
(controls.value as any).autoRotate = false;
|
||||||
timer.value -= 1
|
|
||||||
} else if (timer.value == 0 && !controls.value.autoRotate) {
|
|
||||||
pause()
|
|
||||||
camera.value?.position.set(
|
|
||||||
controls.value.minDistance * 0.5,
|
|
||||||
controls.value.minDistance * 0.5,
|
|
||||||
controls.value.minDistance
|
|
||||||
);
|
|
||||||
(controls.value as any).autoRotate = true;
|
|
||||||
(controls.value as any).autoRotateSpeed = 1;
|
|
||||||
resume()
|
|
||||||
}
|
}
|
||||||
}, 1000)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { onAfterRender } = useLoop()
|
const { onAfterRender } = useLoop()
|
||||||
|
@ -261,22 +253,33 @@ watch(() => props.source, () => {
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
document.addEventListener('click', clickEvent)
|
document.addEventListener('click', clickEvent)
|
||||||
|
|
||||||
document.addEventListener('click', stopTimer)
|
document.addEventListener('click', stopTimer)
|
||||||
|
document.addEventListener('contextmenu', stopTimer)
|
||||||
|
|
||||||
document.addEventListener('touchstart', stopTimer)
|
document.addEventListener('touchstart', stopTimer)
|
||||||
|
document.addEventListener('touchend', stopTimer)
|
||||||
|
document.addEventListener('touchmove', stopTimer)
|
||||||
|
|
||||||
if (sidebar.is_open) {
|
if (sidebar.is_open) {
|
||||||
sidebar.close()
|
sidebar.close()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
document.removeEventListener('click', clickEvent)
|
document.removeEventListener('click', clickEvent)
|
||||||
|
|
||||||
document.removeEventListener('click', stopTimer)
|
document.removeEventListener('click', stopTimer)
|
||||||
|
document.removeEventListener('contextmenu', stopTimer)
|
||||||
|
|
||||||
document.removeEventListener('touchstart', stopTimer)
|
document.removeEventListener('touchstart', stopTimer)
|
||||||
|
document.removeEventListener('touchend', stopTimer)
|
||||||
|
document.removeEventListener('touchmove', stopTimer)
|
||||||
})
|
})
|
||||||
const pointer = reactive({ x: 0, y: 0 })
|
const pointer = reactive({ x: 0, y: 0 })
|
||||||
const stopTimer = () => {
|
const stopTimer = () => {
|
||||||
timer.value = 10;
|
timer.value = 10;
|
||||||
if (controls.value.autoRotate) {
|
if (controls.value && (controls.value as any).autoRotate) {
|
||||||
controls.value.autoRotate = false;
|
(controls.value as any).autoRotate = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const clickEvent = (event: MouseEvent) => {
|
const clickEvent = (event: MouseEvent) => {
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { RouterLink } from 'vue-router';
|
import { RouterLink } from 'vue-router';
|
||||||
import { onClickOutside } from '@vueuse/core'
|
|
||||||
import { usePromoSidebar } from '../../stores/promo_sidebar';
|
import { usePromoSidebar } from '../../stores/promo_sidebar';
|
||||||
import { usePromoScene } from '../../stores/promo_scene';
|
import { usePromoScene } from '../../stores/promo_scene';
|
||||||
|
|
||||||
const sidebar = usePromoSidebar()
|
const sidebar = usePromoSidebar()
|
||||||
const sidebar_scene = usePromoScene()
|
const sidebar_scene = usePromoScene()
|
||||||
const sidebar_obj = ref()
|
const sidebar_obj = ref()
|
||||||
// onClickOutside(sidebar_obj, () => sidebar.close())
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
|
@ -34,7 +32,7 @@ const sidebar_obj = ref()
|
||||||
<input type="checkbox" v-model="item.is_enabled" :id="item.name" :disabled="item.can_not_disable" />
|
<input type="checkbox" v-model="item.is_enabled" :id="item.name" :disabled="item.can_not_disable" />
|
||||||
<label :for="item.name">
|
<label :for="item.name">
|
||||||
<h3>{{ item.name }}</h3>
|
<h3>{{ item.name }}</h3>
|
||||||
<template v-for="p in item.description.split('\n')">
|
<template v-for="p in (item.description || '').split('\n')">
|
||||||
<p>{{ p }}</p>
|
<p>{{ p }}</p>
|
||||||
</template>
|
</template>
|
||||||
</label>
|
</label>
|
||||||
|
@ -57,7 +55,8 @@ const sidebar_obj = ref()
|
||||||
padding: 3rem 2rem 2rem;
|
padding: 3rem 2rem 2rem;
|
||||||
|
|
||||||
@media(max-width:768px) {
|
@media(max-width:768px) {
|
||||||
padding-top: 2rem;;
|
padding-top: 2rem;
|
||||||
|
;
|
||||||
padding-left: 0.75rem;
|
padding-left: 0.75rem;
|
||||||
padding-right: 0.75rem;
|
padding-right: 0.75rem;
|
||||||
max-width: 48%;
|
max-width: 48%;
|
||||||
|
|
|
@ -30,3 +30,9 @@ export function* chunks<T>(arr: T[], n: number): Generator<T[], void> {
|
||||||
yield arr.slice(i, i + n);
|
yield arr.slice(i, i + n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const mobileAndTabletCheck = () => {
|
||||||
|
let check = false;
|
||||||
|
(function (a) { if (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(a) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0, 4))) check = true; })(navigator.userAgent || navigator.vendor || window.opera);
|
||||||
|
return check;
|
||||||
|
};
|
|
@ -34,6 +34,9 @@ interface element3DType {
|
||||||
max_distance?: number,
|
max_distance?: number,
|
||||||
is_enabled: boolean
|
is_enabled: boolean
|
||||||
can_not_disable: boolean
|
can_not_disable: boolean
|
||||||
|
x_pos: number
|
||||||
|
y_pos: number
|
||||||
|
z_pos: number
|
||||||
}
|
}
|
||||||
interface model3DType {
|
interface model3DType {
|
||||||
modelUrl?: string,
|
modelUrl?: string,
|
||||||
|
@ -67,4 +70,14 @@ interface PromoScene {
|
||||||
description?: string
|
description?: string
|
||||||
parent?: number
|
parent?: number
|
||||||
is_enabled: boolean
|
is_enabled: boolean
|
||||||
|
can_not_disable: boolean
|
||||||
|
}
|
||||||
|
interface EnvVars {
|
||||||
|
focus: number,
|
||||||
|
hdr_gainmap?: string,
|
||||||
|
hdr_json?: string,
|
||||||
|
hdr_webp?: string,
|
||||||
|
clear_color?: string,
|
||||||
|
env_displacementmap?: string,
|
||||||
|
env_normalmap?: string
|
||||||
}
|
}
|
|
@ -6,20 +6,12 @@ import './assets/main.scss'
|
||||||
|
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import Home from './components/Home.vue'
|
import Home from './components/Home.vue'
|
||||||
import Projects from './components/Projects.vue'
|
|
||||||
import Game from './components/Game.vue'
|
|
||||||
import Floorplan from './components/Floorplan/index.vue'
|
|
||||||
import FloorplanItem from './components/Floorplan/item.vue'
|
|
||||||
import Promo from './components/Promo/index.vue'
|
import Promo from './components/Promo/index.vue'
|
||||||
import PromoMain from './components/Promo/main.vue'
|
import PromoMain from './components/Promo/main.vue'
|
||||||
import PromoItem from './components/Promo/item.vue'
|
import PromoItem from './components/Promo/item.vue'
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{ path: '/', component: Home },
|
{ path: '/', component: Home },
|
||||||
{ path: '/projects', component: Projects },
|
|
||||||
{ path: '/game', component: Game },
|
|
||||||
{ path: '/floorplan', component: Floorplan },
|
|
||||||
{ path: '/floorplan/:id', component: FloorplanItem },
|
|
||||||
{ path: '/promo', component: Promo },
|
{ path: '/promo', component: Promo },
|
||||||
{ path: '/promo/:page', component: PromoMain },
|
{ path: '/promo/:page', component: PromoMain },
|
||||||
{ path: '/promo/:page/:target', component: PromoMain },
|
{ path: '/promo/:page/:target', component: PromoMain },
|
||||||
|
|
Loading…
Reference in New Issue