添加材质切换,调节功能
This commit is contained in:
42
src/components/CfFlex.vue
Normal file
42
src/components/CfFlex.vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<div class="cf-flex" :style="{
|
||||
alignItems: props.align,
|
||||
justifyContent: props.justify,
|
||||
flexDirection: flexDirection,
|
||||
flexWrap: props.wrap ? 'wrap' : 'nowrap',
|
||||
gap: props.gap
|
||||
}">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang='ts'>
|
||||
import { type Property } from 'csstype';
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
align?: Property.AlignItems;
|
||||
justify?: Property.JustifyContent;
|
||||
vertical?: boolean;
|
||||
wrap?: boolean;
|
||||
gap?: string;
|
||||
reverse?: boolean;
|
||||
}>(), {
|
||||
justify: 'normal',
|
||||
vertical: false,
|
||||
wrap: true
|
||||
});
|
||||
|
||||
const flexDirection = computed(() =>
|
||||
props.vertical ?
|
||||
props.reverse ? 'column-reverse' : 'column'
|
||||
: props.reverse ? 'row-reverse' : 'row')
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.cf-flex
|
||||
{
|
||||
display: flex;
|
||||
}
|
||||
</style>
|
293
src/components/MaterialAdjuster.vue
Normal file
293
src/components/MaterialAdjuster.vue
Normal file
@@ -0,0 +1,293 @@
|
||||
<template>
|
||||
<div vertical class="material-adjuster">
|
||||
<div class="adjust-section">
|
||||
<h3>模型预览</h3>
|
||||
<label>选择模型样式</label>
|
||||
<select v-model="CurrGeometry">
|
||||
<option v-for="name in Geometries" :value="name">{{ name }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="adjust-section" v-if="debugMode">
|
||||
<h3>纹理选择(DEBUG)</h3>
|
||||
<label>纹理链接</label>
|
||||
<input v-model.trim="textureLink" type="text" placeholder="在此键入纹理图像的URL..." />
|
||||
<button class="btn-primary" @click="scene.ChangeTextureAsync(textureLink)"
|
||||
style="margin-left: 1em;">应用</button>
|
||||
</div>
|
||||
<div class="adjust-section">
|
||||
<h3>材质调节</h3>
|
||||
|
||||
<label>金属度</label>
|
||||
<CfFlex gap="1em">
|
||||
<input v-model="model.metallic" type="range" min="0" max="1" step="0.01" />
|
||||
<span>{{ model.metallic }}</span>
|
||||
</CfFlex>
|
||||
|
||||
<label>粗糙度</label>
|
||||
<CfFlex gap="1em">
|
||||
<input v-model="model.roughness" type="range" min="0" max="1" step="0.01" />
|
||||
<span>{{ model.roughness }}</span>
|
||||
</CfFlex>
|
||||
|
||||
<label>法线强度</label>
|
||||
<CfFlex gap="1em">
|
||||
<input v-model="model.normalScale" type="range" min="0" max="1" step="0.01" />
|
||||
<span>{{ model.normalScale }}</span>
|
||||
</CfFlex>
|
||||
|
||||
<label>高光</label>
|
||||
<CfFlex gap="1em">
|
||||
<input v-model="model.emissiveIntensity" type="range" min="0" max="1" step="0.01" />
|
||||
<span>{{ model.emissiveIntensity }}</span>
|
||||
</CfFlex>
|
||||
</div>
|
||||
|
||||
<div class="adjust-section">
|
||||
<h3>纹理调节</h3>
|
||||
<label>启用纹理</label>
|
||||
<input type="checkbox" v-model="enableTexture" />
|
||||
|
||||
<label>平铺U</label>
|
||||
<select v-model="textureAdjustment.wrapS">
|
||||
<option value="0">镜像平铺</option>
|
||||
<option value="1">平铺</option>
|
||||
<option value="2">展开</option>
|
||||
</select>
|
||||
|
||||
<label>平铺V</label>
|
||||
<select v-model="textureAdjustment.wrapT">
|
||||
<option value="0">镜像平铺</option>
|
||||
<option value="1">平铺</option>
|
||||
<option value="2">展开</option>
|
||||
</select>
|
||||
|
||||
<label>旋转</label>
|
||||
<input v-model="textureAdjustment.rotation" type="number" step="0.01" min="0" max="359" />
|
||||
|
||||
<div>
|
||||
<label>偏移</label>
|
||||
<span style="margin-right: 0.5em;">X</span>
|
||||
<input v-model="textureAdjustment.moveX" type="number" step="0.01" style="max-width: 60px;" />
|
||||
 
|
||||
 
|
||||
<span style="margin-right: 0.5em;">Y</span>
|
||||
<input v-model="textureAdjustment.moveY" type="number" step="0.01" style="max-width: 60px;" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label>尺寸</label>
|
||||
<span style="margin-right: 0.5em;">长</span>
|
||||
<input v-model="textureAdjustment.repeatX" type="number" step="0.01" style="max-width: 60px;" />
|
||||
 
|
||||
 
|
||||
<span style="margin-right: 0.5em;">宽</span>
|
||||
<input v-model="textureAdjustment.repeatY" type="number" step="0.01" style="max-width: 60px;" />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="adjust-section">
|
||||
<h3>操作</h3>
|
||||
<fieldset v-if="debugMode" style="margin: 1em 0;">
|
||||
<legend>DEBUG</legend>
|
||||
<label>上传路径ID</label>
|
||||
<input v-model="materialRequest.dirId" type="text" placeholder="材质路径ID" />
|
||||
</fieldset>
|
||||
<label>材质名</label>
|
||||
<input v-model="materialRequest.materialName" type="text" placeholder="材质名" />
|
||||
|
||||
<CfFlex gap="1em">
|
||||
<button class="btn-success" style="min-width: 110px;" @click="HandleUpload">上传</button>
|
||||
<button class="btn-danger" style="min-width: 110px;" @click="HandleCancel">取消</button>
|
||||
</CfFlex>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang='ts'>
|
||||
import { ref, reactive, watch } from "vue"
|
||||
import { useScene, type TextureAdjustment } from "../stores/sceneStore";
|
||||
import { storeToRefs } from "pinia";
|
||||
import CfFlex from "./CfFlex.vue";
|
||||
import { DirectoryId } from "../api/Request";
|
||||
import { IsNullOrWhitespace } from "../helpers/helper.string";
|
||||
|
||||
const scene = useScene();
|
||||
|
||||
const debugMode = ref(true);
|
||||
const { CurrGeometry, Geometries, Material } = storeToRefs(scene);
|
||||
const enableTexture = ref(Material.value.useMap);
|
||||
const textureLink = ref('');
|
||||
const textureAdjustment = reactive<TextureAdjustment>({
|
||||
wrapS: 0,
|
||||
wrapT: 0,
|
||||
rotation: 0,
|
||||
repeatX: 1,
|
||||
repeatY: 1,
|
||||
moveX: 0,
|
||||
moveY: 0
|
||||
});
|
||||
const uploading = ref(false);
|
||||
const model = reactive({
|
||||
metallic: Material.value.matalness,
|
||||
roughness: Material.value.roughness,
|
||||
normalScale: Material.value.bumpScale,
|
||||
emissiveIntensity: Material.value.specular
|
||||
});
|
||||
const materialRequest = reactive({
|
||||
dirId: DirectoryId.MaterialDir, // 正常来说是2
|
||||
materialName: ''
|
||||
});
|
||||
|
||||
watch(model, async (val) => {
|
||||
await UpdateMaterial();
|
||||
});
|
||||
|
||||
watch(enableTexture, async (val) => {
|
||||
await EnableTexture(val);
|
||||
});
|
||||
|
||||
watch(textureAdjustment, async (val) => {
|
||||
UpdateTexture();
|
||||
});
|
||||
|
||||
async function UpdateMaterial() {
|
||||
Material.value.matalness = model.metallic;
|
||||
Material.value.roughness = model.roughness;
|
||||
Material.value.bumpScale = model.normalScale;
|
||||
Material.value.specular = model.emissiveIntensity;
|
||||
// Material.value.side = DoubleSide;
|
||||
await scene.UpdateMaterialAsync();
|
||||
}
|
||||
|
||||
function UpdateTexture() {
|
||||
scene.UpdateTexture(textureAdjustment);
|
||||
}
|
||||
|
||||
async function EnableTexture(enable: boolean) {
|
||||
Material.value.useMap = enable;
|
||||
await scene.UpdateMaterialAsync();
|
||||
}
|
||||
|
||||
async function HandleUpload() {
|
||||
try {
|
||||
if (IsNullOrWhitespace(materialRequest.materialName)) {
|
||||
alert('材质名称不可为空');
|
||||
return;
|
||||
}
|
||||
|
||||
uploading.value = true;
|
||||
await scene.UploadMaterialAsync(materialRequest);
|
||||
uploading.value = false;
|
||||
alert("上传成功");
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
alert("上传材质出错,请检查网络连接或联系管理员");
|
||||
}
|
||||
}
|
||||
|
||||
async function HandleCancel() {
|
||||
// TODO: 触发取消事件
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.material-adjuster
|
||||
{
|
||||
padding: 1rem;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.adjust-section
|
||||
{
|
||||
border-radius: 8px;
|
||||
border: 1px solid #ccc;
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
h3
|
||||
{
|
||||
margin: 0 0 0.5rem 0;
|
||||
}
|
||||
|
||||
label
|
||||
{
|
||||
display: block;
|
||||
}
|
||||
|
||||
select
|
||||
{
|
||||
width: 220px;
|
||||
padding: 4px;
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
input[type="range"]
|
||||
{
|
||||
width: 220px;
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
input[type="text"]
|
||||
{
|
||||
width: 220px;
|
||||
padding: 4px;
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
input[type="number"]
|
||||
{
|
||||
width: 220px;
|
||||
padding: 4px;
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.btn-success
|
||||
{
|
||||
background-color: #4caf50;
|
||||
color: white;
|
||||
padding: 0.5rem 1rem;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-success:hover
|
||||
{
|
||||
background-color: #3e8e41;
|
||||
}
|
||||
|
||||
.btn-danger
|
||||
{
|
||||
background-color: #f44336;
|
||||
color: white;
|
||||
padding: 0.5rem 1rem;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-danger:hover
|
||||
{
|
||||
background-color: #da190b;
|
||||
}
|
||||
|
||||
.btn-primary
|
||||
{
|
||||
background-color: #2196f3;
|
||||
color: white;
|
||||
padding: 0.5rem 1rem;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-primary:hover
|
||||
{
|
||||
background-color: #0a7ed2;
|
||||
}
|
||||
</style>
|
@@ -1,41 +1,32 @@
|
||||
<template>
|
||||
<div ref="container" style="width: 800px;height: 800px;"></div>
|
||||
{{ CurGeometryName }}
|
||||
<div v-for="geo in geometries">
|
||||
<button @click="changeGeometry(geo[0])">{{ geo[0] }}</button>
|
||||
</div>
|
||||
<CfFlex class="material-view">
|
||||
<div ref="container" style="width: 100%; height: 100%; flex: 3; box-sizing: border-box;" />
|
||||
<MaterialAdjuster style="flex: 1;overflow-y: auto; width: 100%; height: 100%; box-sizing: border-box;" />
|
||||
</CfFlex>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { onMounted, useTemplateRef } from 'vue';
|
||||
import { MaterialEditor } from '../common/MaterialEditor';
|
||||
import { PhysicalMaterialRecord } from 'webcad_ue4_api';
|
||||
import MaterialAdjuster from './MaterialAdjuster.vue';
|
||||
import { useScene } from '../stores/sceneStore';
|
||||
import CfFlex from './CfFlex.vue';
|
||||
|
||||
const scene = useScene();
|
||||
const container = useTemplateRef<HTMLElement>('container');
|
||||
|
||||
|
||||
let editor:MaterialEditor = MaterialEditor.GetInstance();
|
||||
const geometries = editor.Geometrys;
|
||||
const material = new PhysicalMaterialRecord();
|
||||
|
||||
const CurGeometryName = editor.CurGeometryName;
|
||||
onMounted(() => {
|
||||
editor.SetViewer(container.value);
|
||||
editor.setMaterial(material);
|
||||
scene.Initial(container.value);
|
||||
});
|
||||
|
||||
const view = editor.Viewer;
|
||||
view.OnSize(800, 800);
|
||||
view.ZoomAll();
|
||||
view.Zoom(2.1);
|
||||
})
|
||||
</script>
|
||||
|
||||
function changeGeometry(geoName:string) {
|
||||
CurGeometryName.value = geoName;
|
||||
let geo = editor.Geometrys.get(CurGeometryName.value);
|
||||
if (geo) {
|
||||
editor.ShowMesh.geometry = geo;
|
||||
editor.Viewer.UpdateRender();
|
||||
}
|
||||
<style scoped>
|
||||
.material-view
|
||||
{
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
</style>
|
Reference in New Issue
Block a user