forked from mns/mini-skamja
three js module
This commit is contained in:
parent
2faf0979fd
commit
e03889db97
|
@ -2,37 +2,28 @@
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
import { useGLTF } from '@tresjs/cientos'
|
import { useGLTF } from '@tresjs/cientos'
|
||||||
import { useTresContext } from '@tresjs/core'
|
import { useTresContext } from '@tresjs/core'
|
||||||
import { Vector3 } from 'three'
|
import { degToRad } from 'three/src/math/MathUtils.js'
|
||||||
import { ref, onMounted } from 'vue'
|
|
||||||
|
|
||||||
const { camera, controls } = useTresContext()
|
const { camera, controls } = useTresContext()
|
||||||
|
|
||||||
const { scene: obj } = await useGLTF('/models/bench2_export-v1.glb')
|
const { scene } = await useGLTF('/models/table.glb')
|
||||||
const scale = ref(1) // Масштаб объекта
|
const object = calculateScaleToFit(
|
||||||
|
scene,
|
||||||
onMounted(() => {
|
camera,
|
||||||
if (obj && camera.value && controls.value) {
|
controls,
|
||||||
const distance = 10
|
3,
|
||||||
const cameraOffset = { x: 1, y: 0.75, z: -1.25 } // Смещение камеры
|
{
|
||||||
|
x: Math.sin(degToRad(-30)), // Смещение по оси X
|
||||||
// Вычисляем масштаб и позицию камеры
|
y: 1, // Нет смещения по оси Y
|
||||||
const { scale: objectScale } = calculateScaleToFit(
|
z: -Math.cos(degToRad(0)) // Смещение по оси Z
|
||||||
obj,
|
|
||||||
camera.value as any,
|
|
||||||
controls.value as any,
|
|
||||||
distance,
|
|
||||||
cameraOffset
|
|
||||||
)
|
|
||||||
|
|
||||||
scale.value = objectScale
|
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<TresGroup :position-y="0" :scale="new Vector3(scale, scale, scale)">
|
<TresGroup>
|
||||||
<ModelItem :model="obj" :target="[0, 0, 0]" />
|
<ModelItem :model="object" :target="[0, 0, 0]" />
|
||||||
</TresGroup>
|
</TresGroup>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</template>
|
</template>
|
|
@ -2,37 +2,28 @@
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
import { useGLTF } from '@tresjs/cientos'
|
import { useGLTF } from '@tresjs/cientos'
|
||||||
import { useTresContext } from '@tresjs/core'
|
import { useTresContext } from '@tresjs/core'
|
||||||
import { Vector3 } from 'three'
|
import { degToRad } from 'three/src/math/MathUtils.js'
|
||||||
import { ref, onMounted } from 'vue'
|
|
||||||
|
|
||||||
const { camera, controls } = useTresContext()
|
const { camera, controls } = useTresContext()
|
||||||
|
|
||||||
const { scene: obj } = await useGLTF('/models/bench_export-v1.glb')
|
const { scene } = await useGLTF('/models/bench.glb')
|
||||||
const scale = ref(1) // Масштаб объекта
|
const object = calculateScaleToFit(
|
||||||
|
scene,
|
||||||
onMounted(() => {
|
camera,
|
||||||
if (obj && camera.value && controls.value) {
|
controls,
|
||||||
const distance = 10
|
2,
|
||||||
const cameraOffset = { x: -1, y: 0.75, z: -1.25 } // Смещение камеры
|
{
|
||||||
|
x: Math.sin(degToRad(30)), // Смещение по оси X
|
||||||
// Вычисляем масштаб и позицию камеры
|
y: 1, // Нет смещения по оси Y
|
||||||
const { scale: objectScale } = calculateScaleToFit(
|
z: -Math.cos(degToRad(0)) // Смещение по оси Z
|
||||||
obj,
|
|
||||||
camera.value as any,
|
|
||||||
controls.value as any,
|
|
||||||
distance,
|
|
||||||
cameraOffset
|
|
||||||
)
|
|
||||||
|
|
||||||
scale.value = objectScale
|
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<TresGroup :position-y="0" :scale="new Vector3(scale, scale, scale)">
|
<TresGroup>
|
||||||
<ModelItem :model="obj" :target="[0, 0, 0]" />
|
<ModelItem :model="object" :target="[0, 0, 0]" />
|
||||||
</TresGroup>
|
</TresGroup>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</template>
|
</template>
|
|
@ -3,9 +3,10 @@ import {
|
||||||
PCFSoftShadowMap,
|
PCFSoftShadowMap,
|
||||||
CineonToneMapping,
|
CineonToneMapping,
|
||||||
PMREMGenerator,
|
PMREMGenerator,
|
||||||
} from 'three';
|
CanvasTexture
|
||||||
|
} from 'three'
|
||||||
|
|
||||||
import { GainMapLoader, } from '@monogrid/gainmap-js'
|
import { GainMapLoader } from '@monogrid/gainmap-js'
|
||||||
|
|
||||||
const { scene, renderer, camera } = useTresContext()
|
const { scene, renderer, camera } = useTresContext()
|
||||||
|
|
||||||
|
@ -15,26 +16,49 @@ renderer.value.toneMappingExposure = 1
|
||||||
renderer.value.shadowMap.enabled = true
|
renderer.value.shadowMap.enabled = true
|
||||||
renderer.value.shadowMap.type = PCFSoftShadowMap
|
renderer.value.shadowMap.type = PCFSoftShadowMap
|
||||||
|
|
||||||
const pmremGenerator = new PMREMGenerator(renderer.value);
|
const pmremGenerator = new PMREMGenerator(renderer.value)
|
||||||
pmremGenerator.compileEquirectangularShader();
|
pmremGenerator.compileEquirectangularShader()
|
||||||
|
|
||||||
|
function createGradientTexture () {
|
||||||
|
const canvas = document.createElement('canvas')
|
||||||
|
canvas.width = 256
|
||||||
|
canvas.height = 256
|
||||||
|
|
||||||
|
const ctx = canvas.getContext('2d')
|
||||||
|
if (!ctx) return null
|
||||||
|
|
||||||
|
// Создаем линейный градиент
|
||||||
|
const gradient = ctx.createLinearGradient(0, 0, canvas.width, canvas.height)
|
||||||
|
gradient.addColorStop(0, '#ff7eb3') // Верхний цвет
|
||||||
|
gradient.addColorStop(1, '#4f46e5') // Нижний цвет
|
||||||
|
|
||||||
|
// Заполняем холст градиентом
|
||||||
|
ctx.fillStyle = gradient
|
||||||
|
ctx.fillRect(0, 0, canvas.width, canvas.height)
|
||||||
|
|
||||||
|
// Создаем текстуру из холста
|
||||||
|
return new CanvasTexture(canvas)
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const loader = new GainMapLoader(renderer.value)
|
const loader = new GainMapLoader(renderer.value)
|
||||||
const result = await loader.loadAsync([
|
const result = await loader.loadAsync([
|
||||||
'hdrmaps/hdr.webp',
|
'hdrmaps/hdr.webp',
|
||||||
'hdrmaps/hdr-gainmap.webp',
|
'hdrmaps/hdr-gainmap.webp',
|
||||||
'hdrmaps/hdr.json',
|
'hdrmaps/hdr.json'
|
||||||
])
|
])
|
||||||
if (renderer.value && camera.value) {
|
if (renderer.value && camera.value) {
|
||||||
renderer.value.render(scene.value, camera.value)
|
renderer.value.render(scene.value, camera.value)
|
||||||
}
|
}
|
||||||
const exrCubeRenderTarget = pmremGenerator.fromEquirectangular(result.renderTarget.texture);
|
const exrCubeRenderTarget = pmremGenerator.fromEquirectangular(
|
||||||
const newEnvMap = exrCubeRenderTarget ? exrCubeRenderTarget.texture : null;
|
result.renderTarget.texture
|
||||||
|
)
|
||||||
|
const newEnvMap = exrCubeRenderTarget ? exrCubeRenderTarget.texture : null
|
||||||
|
|
||||||
scene.value.environment = newEnvMap
|
scene.value.environment = newEnvMap
|
||||||
scene.value.environmentIntensity = 1.25
|
scene.value.environmentIntensity = 1.25
|
||||||
scene.value.environmentRotation.z = 0.25
|
scene.value.environmentRotation.z = 0.25
|
||||||
result.renderTarget.texture.dispose();
|
result.renderTarget.texture.dispose()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
<template></template>
|
<template></template>
|
|
@ -14,15 +14,26 @@ const props = defineProps({
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const camera = ref()
|
||||||
|
const controls = ref()
|
||||||
|
|
||||||
const controlsState = reactive({
|
const controlsState = reactive({
|
||||||
// minDistance: 2,
|
minDistance: 2,
|
||||||
// maxDistance: 10,
|
maxDistance: 20,
|
||||||
enablePan: false,
|
enablePan: false
|
||||||
enableZoom: false,
|
// enableZoom: false,
|
||||||
maxPolarAngle: Math.PI / 2 - 0.2
|
// maxPolarAngle: Math.PI / 2 - 0.2
|
||||||
})
|
})
|
||||||
|
|
||||||
const toggleModal = () => {}
|
const toggleModal = () => {}
|
||||||
|
|
||||||
|
const changeDistance = (v = 1) => {
|
||||||
|
if (camera.value && controls.value) {
|
||||||
|
const distance = camera.value.position.distanceTo(new Vector3(0, 0, 0))
|
||||||
|
const r = distance + v
|
||||||
|
camera.value.position.normalize().multiplyScalar(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -32,14 +43,45 @@ const toggleModal = () => {}
|
||||||
<template #fallback>
|
<template #fallback>
|
||||||
<div class="fallback">Загрузка 3D модели</div>
|
<div class="fallback">Загрузка 3D модели</div>
|
||||||
</template>
|
</template>
|
||||||
<TresCanvas height="600">
|
<TresCanvas height="600" clear-color="#e2e8f0">
|
||||||
<TresPerspectiveCamera />
|
<TresPerspectiveCamera
|
||||||
<OrbitControls v-bind="controlsState" make-default />
|
:position="new Vector3(-7, 2, 4)"
|
||||||
|
ref="camera"
|
||||||
|
/>
|
||||||
|
<OrbitControls v-bind="controlsState" ref="controls" make-default />
|
||||||
<ModelEnv />
|
<ModelEnv />
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<component :is="types[props.type]" />
|
<component :is="types[props.type]" />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</TresCanvas>
|
</TresCanvas>
|
||||||
|
<div class="canvas-icons">
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
@click.prevent="changeDistance(-0.5)"
|
||||||
|
:class="[
|
||||||
|
{
|
||||||
|
disabled: camera
|
||||||
|
? camera.position.distanceTo(new Vector3(0, 0, 0)) <= controlsState.minDistance
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<Icon name="mdi:plus-circle-outline" />
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
@click.prevent="changeDistance(0.5)"
|
||||||
|
:class="[
|
||||||
|
{
|
||||||
|
disabled: camera
|
||||||
|
? camera.position.distanceTo(new Vector3(0, 0, 0)) >= controlsState.maxDistance
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<Icon name="mdi:minus-circle-outline" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</ClientOnly>
|
</ClientOnly>
|
||||||
</div>
|
</div>
|
||||||
<button @click.prevent="toggleModal">Рассчитать</button>
|
<button @click.prevent="toggleModal">Рассчитать</button>
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,38 +1,54 @@
|
||||||
import { Box3, PerspectiveCamera, Vector3 } from 'three'
|
import { Box3, PerspectiveCamera, Vector3, Mesh, Camera } from 'three'
|
||||||
import { degToRad } from 'three/src/math/MathUtils.js';
|
import { degToRad } from 'three/src/math/MathUtils.js'
|
||||||
|
|
||||||
/**
|
export default function calculateScaleToFit(
|
||||||
* Вычисляет масштаб объекта и позицию камеры, чтобы объект полностью помещался в видимой области.
|
|
||||||
* @param object - Объект Three.js (например, загруженная модель).
|
|
||||||
* @param camera - Экземпляр камеры.
|
|
||||||
* @param controls - Экземпляр OrbitControls.
|
|
||||||
* @param distance - Расстояние от камеры до объекта.
|
|
||||||
* @param cameraOffset - Коэффициенты смещения камеры (x, y, z).
|
|
||||||
*/
|
|
||||||
export default function (
|
|
||||||
object: any,
|
object: any,
|
||||||
camera: PerspectiveCamera,
|
camera: ComputedRef<Camera | undefined>,
|
||||||
controls: any,
|
controls: any,
|
||||||
distance: number,
|
distance: number,
|
||||||
cameraOffset: { x: number; y: number; z: number }
|
cameraOffset: { x: number; y: number; z: number }
|
||||||
) {
|
) {
|
||||||
|
// Создаем новый BoundingBox
|
||||||
|
const bbox = new Box3()
|
||||||
|
|
||||||
// Обновляем мировую матрицу объекта
|
// Обновляем мировую матрицу объекта
|
||||||
object.updateWorldMatrix(true, true)
|
object.updateWorldMatrix(true, true)
|
||||||
|
|
||||||
const fov = camera.fov // Угол обзора камеры
|
// Перебираем все дочерние элементы объекта
|
||||||
const aspect = camera.aspect // Соотношение сторон
|
object.traverse((child: any) => {
|
||||||
|
if (child.isMesh) {
|
||||||
|
// Добавляем геометрию каждого меша в bounding box
|
||||||
|
const geometry = child.geometry
|
||||||
|
if (geometry) {
|
||||||
|
geometry.computeBoundingBox() // Убедимся, что bounding box геометрии вычислен
|
||||||
|
const meshBBox = geometry.boundingBox.clone()
|
||||||
|
if (meshBBox) {
|
||||||
|
// Применяем мировую матрицу к bounding box
|
||||||
|
meshBBox.applyMatrix4(child.matrixWorld)
|
||||||
|
bbox.union(meshBBox) // Объединяем с общим bounding box
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const bbox = new Box3().setFromObject(object) // Вычисляем bounding box
|
// Вычисляем центр и размер bounding box
|
||||||
const center = new Vector3()
|
const center = new Vector3()
|
||||||
bbox.getCenter(center) // Центр объекта
|
bbox.getCenter(center)
|
||||||
|
|
||||||
const size = new Vector3()
|
const size = new Vector3()
|
||||||
bbox.getSize(size) // Размеры объекта
|
bbox.getSize(size)
|
||||||
|
|
||||||
// Рассчитываем масштаб, чтобы объект полностью помещался в видимой области
|
// Центрируем объект
|
||||||
const vFOV = degToRad(fov) // Угол обзора в радианах
|
object.position.sub(center)
|
||||||
const visibleHeight = 2 * Math.tan(vFOV / 2) * distance // Видимая высота
|
object.updateWorldMatrix(true, true)
|
||||||
const visibleWidth = visibleHeight * aspect // Видимая ширина
|
|
||||||
|
// Рассчитываем масштаб
|
||||||
|
const fov = (camera.value as PerspectiveCamera).fov
|
||||||
|
const aspect = (camera.value as PerspectiveCamera).aspect
|
||||||
|
|
||||||
|
const vFOV = degToRad(fov)
|
||||||
|
const visibleHeight = 2 * Math.tan(vFOV / 2) * distance
|
||||||
|
const visibleWidth = visibleHeight * aspect
|
||||||
|
|
||||||
const scaleX = visibleWidth / size.x
|
const scaleX = visibleWidth / size.x
|
||||||
const scaleY = visibleHeight / size.y
|
const scaleY = visibleHeight / size.y
|
||||||
|
@ -40,18 +56,20 @@ export default function (
|
||||||
|
|
||||||
const scale = Math.min(scaleX, scaleY, scaleZ)
|
const scale = Math.min(scaleX, scaleY, scaleZ)
|
||||||
|
|
||||||
// Позиционируем камеру с учетом смещения
|
// Масштабируем объект
|
||||||
camera.position.set(
|
object.scale.setScalar(scale)
|
||||||
center.x + distance * cameraOffset.x,
|
object.updateWorldMatrix(true, true)
|
||||||
center.y + distance * cameraOffset.y,
|
|
||||||
center.z + distance * cameraOffset.z
|
|
||||||
)
|
|
||||||
|
|
||||||
// Устанавливаем цель для OrbitControls
|
if (camera.value) {
|
||||||
if (controls) {
|
const [x, y, z] = Object.values(cameraOffset).map(el => el * distance);
|
||||||
controls.target.set(center.x, center.y, center.z)
|
camera.value.position.set(x, y, z);
|
||||||
controls.update() // Обновляем контроллы
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return { scale, center }
|
// Настройка OrbitControls
|
||||||
|
if (controls.value) {
|
||||||
|
controls.value.target.set(...Object.values(center.multiplyScalar(1)))
|
||||||
|
controls.value.update()
|
||||||
|
}
|
||||||
|
|
||||||
|
return object
|
||||||
}
|
}
|
Loading…
Reference in New Issue