star_mod_renderer
Advanced tools
Comparing version 1.0.39 to 1.0.40
import * as THREE from 'star_mod_custom_three'; | ||
// TODO: 1. 双指放大缩小事件 | ||
@@ -3,0 +4,0 @@ class EditorControls extends THREE.EventDispatcher { |
@@ -0,0 +0,0 @@ export declare class Signal { |
@@ -0,0 +0,0 @@ /* eslint-disable no-unused-vars */ |
@@ -122,7 +122,8 @@ import * as THREE from 'star_mod_custom_three'; | ||
]; | ||
const textureCube = new THREE.CubeTextureLoader().load(urls); | ||
textureCube.format = THREE.RGBAFormat; | ||
resolve({ | ||
name: envMapMenu[name].name, | ||
data: textureCube, | ||
const textureCube = new THREE.CubeTextureLoader().load(urls, () => { | ||
textureCube.format = THREE.RGBAFormat; | ||
resolve({ | ||
name: envMapMenu[name].name, | ||
data: textureCube, | ||
}); | ||
}); | ||
@@ -133,2 +134,14 @@ }); | ||
export async function getEnvMapList () { | ||
const list = []; | ||
list.push(getEnvMapByName("dreamland", process.env.VUE_APP_NAME)); | ||
list.push(getEnvMapByName("sky", process.env.VUE_APP_NAME)); | ||
list.push(getEnvMapByName("dusk", process.env.VUE_APP_NAME)); | ||
return new Promise((resolve) => { | ||
Promise.all(list).then(res => { | ||
resolve(res); | ||
}) | ||
}); | ||
} | ||
export const SCENE_BACKGROUND_COLOR = 'rgb(68, 68, 68)'; | ||
@@ -135,0 +148,0 @@ export const AMBIENT_LIGHT_COLOR = '#c2c2c2'; |
@@ -0,0 +0,0 @@ import { Cache } from './Cache.js'; |
@@ -0,0 +0,0 @@ import { |
@@ -0,0 +0,0 @@ import { |
@@ -0,0 +0,0 @@ // 对比两个文件内容,同返回 true 异为 false |
export * from './diff'; | ||
export * from './loader'; | ||
export * from './detect'; |
@@ -59,3 +59,2 @@ import { FBXLoader } from "./FBXLoader"; | ||
// TODO: 此处增加判断,如果该文件并非 tga 文件,则判断 Url 后缀是否为 png 或 jpg,如果是,则调用 loadPNGFile | ||
if (type === "tga" && url.endsWith('.tga')) { | ||
@@ -62,0 +61,0 @@ const res = await loadTGAFile(url); |
@@ -0,0 +0,0 @@ /* eslint-disable no-fallthrough */ |
export * from './model'; |
@@ -61,3 +61,3 @@ import * as THREE from 'star_mod_custom_three'; | ||
this.transModelByCoord(assembleModel, trans); | ||
console.log('assembleModel', assembleModel); | ||
return assembleModel; | ||
@@ -335,27 +335,38 @@ } | ||
updateModelMaterialType(channel, type) { | ||
this.model.traverse(async child => { | ||
if (child.material) { | ||
const material = Array.isArray(child.material) ? child.material[channel] : child.material; | ||
if (material.materialType) { | ||
return new Promise((resolve) => { | ||
this.model.traverse(child => { | ||
if (child.material) { | ||
const material = Array.isArray(child.material) ? child.material[channel] : child.material; | ||
if (type === GLASS_MATERIAL_KEY || type === FEMALE_BODY_MATERIAL_KEY || type === MALE_BODY_MATERIAL_KEY) { | ||
material.dispose(); | ||
if (Array.isArray(child.material)) { | ||
this.createSpecificMaterial(type).then(res => { | ||
child.material[channel] = res; | ||
child.material[channel].needsUpdate = true; | ||
resolve(); | ||
}); | ||
} else { | ||
this.createSpecificMaterial(type).then(res => { | ||
child.material = res; | ||
child.material.needsUpdate = true; | ||
resolve(); | ||
}); | ||
} | ||
} else { | ||
if (material.materialType) { | ||
material.dispose(); | ||
} | ||
if (Array.isArray(child.material)) { | ||
child.material[channel] = this.createStandardMaterial(); | ||
child.material[channel].needsUpdate = true; | ||
resolve(); | ||
} else { | ||
child.material = this.createStandardMaterial(); | ||
child.material.needsUpdate = true; | ||
resolve(); | ||
} | ||
} | ||
if (type === GLASS_MATERIAL_KEY || type === FEMALE_BODY_MATERIAL_KEY || type === MALE_BODY_MATERIAL_KEY) { | ||
material.dispose(); | ||
if (Array.isArray(child.material)) { | ||
child.material[channel] = await this.createSpecificMaterial(type); | ||
child.material[channel].needsUpdate = true; | ||
} else { | ||
child.material = await this.createSpecificMaterial(type); | ||
child.material.needsUpdate = true; | ||
} | ||
} | ||
} | ||
}) | ||
}); | ||
}); | ||
} | ||
@@ -455,3 +466,3 @@ | ||
setModelMaterialEnvMap(envMap, renderer) { | ||
setModelMaterialEnvMap(envMap) { | ||
if (!envMap) { | ||
@@ -467,9 +478,5 @@ this.model.traverse(child => { | ||
// TODO: 制空时取消环境贴图 | ||
const crt = new THREE.WebGLCubeRenderTarget(envMap.image.height); | ||
crt.fromEquirectangularTexture(renderer, envMap); | ||
const texture = crt.texture; | ||
this.model.traverse(child => { | ||
if (child.material) { | ||
child.material.envMap = texture; | ||
child.material.envMap = envMap; | ||
child.material.envMapIntensity = 1; | ||
@@ -476,0 +483,0 @@ child.material.needsUpdate = true; |
@@ -0,0 +0,0 @@ export declare function isMesh(key: string): boolean; |
@@ -0,0 +0,0 @@ export function isMesh(key) { |
@@ -9,3 +9,3 @@ import * as THREE from 'star_mod_custom_three'; | ||
dom: HTMLElement | null; | ||
mode: 'editor' | 'render'; | ||
mode: 'edit' | 'preview'; | ||
scene: THREE.Scene | null; | ||
@@ -24,17 +24,33 @@ camera: THREE.PerspectiveCamera | THREE.OrthographicCamera | null; | ||
currSelectModel: THREE.Object3D | null; | ||
options: { | ||
mode: string; | ||
platform: string; | ||
width: number; | ||
height: number; | ||
autoResize: boolean; | ||
autoClear: boolean; | ||
isShowModelInfo: boolean; | ||
isShowSelectionBox: boolean; | ||
isShowSceneEnv: boolean; | ||
isNeedScreenshot: boolean; | ||
}; | ||
// 渲染器初始化 | ||
constructor(options: { mode?: 'editor' | 'render'; dom: HTMLElement; }); | ||
constructor(options: { dom: HTMLElement; options: any; }); | ||
getCurrMode(mode: 'editor' | 'render'): { isEditor?: boolean; isRender?: boolean; }; | ||
getCurrMode(mode: 'edit' | 'preview'): { isEdit?: boolean; isPreview?: boolean; }; | ||
initCommonAbility(): void; | ||
initEditorAbility(): void; | ||
initEditAbility(): void; | ||
initRenderer(): void; | ||
initPreviewAbility(): void; | ||
// 相机初始化与控制器 | ||
initCamera(): void; | ||
initBaseScene(): void; | ||
changePerspectiveCamera(): void; | ||
@@ -53,2 +69,4 @@ | ||
initEditSignal(): void; | ||
changeAmbientLight(options: { color: string; intensity: number; }): void; | ||
@@ -64,3 +82,3 @@ | ||
addEditorEventListener(): void; | ||
addEditEventListener(): void; | ||
@@ -125,4 +143,8 @@ getMousePosition(dom: HTMLElement, x: number, y: number): [number, number]; | ||
setPreviewData(previewData: any): void; | ||
update(): void; | ||
customUpdate(): void; | ||
getRenderDom(): HTMLElement; | ||
@@ -135,19 +157,29 @@ | ||
export declare class Render extends RenderBase { | ||
generateCurrFrameFlag: boolean; | ||
currFrame: HTMLImageElement | null; | ||
modelRender: ModelProcessor | null; | ||
mainModels: any[]; | ||
modelsLoader: any[]; | ||
auxiliaryModels: any; | ||
previewData: any; | ||
animations: any[]; | ||
constructor(options: { container: HTMLElement | null; platform: string; isShowScene: boolean; }); | ||
constructor(options: { dom: HTMLElement; options: any; }); | ||
setRenderModel(model: Model): void; | ||
initSceneEnv(): void; | ||
handleRenderModelInfo(): void; | ||
initRenderSign(): void; | ||
setRenderPreviewData(previewData: any): void; | ||
setMainModel(any): void; | ||
customUpdate(): void; | ||
setCombinationModels(any): void; | ||
getCurrFrame(): Promise<HTMLImageElement>; | ||
setFashionAnimations(any): void; | ||
getAllEnvMap(any): void; | ||
start(): void; | ||
getCurrPreviewStoreData(): any; | ||
getCurrFrame(): any; | ||
static drawTgaCanvas(url: string): Promise<HTMLImageElement>; | ||
} |
export * from './render.js'; | ||
export * from './render.base.js'; | ||
export * from './default.js'; |
{ | ||
"name": "star_mod_renderer", | ||
"version": "1.0.39", | ||
"version": "1.0.40", | ||
"description": "star mod project render auxiliary ", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -0,1 +1,70 @@ | ||
/** | ||
渲染器基类,提供 Render 的基础能力,根据不同模式选择启用不同的功能 | ||
constructor 接受参数: | ||
- dom: 承载 canvas 的节点,如果没有将会在执行阶段自行创建 | ||
- options: 渲染器配置项 | ||
- mode: 渲染模式,可选值为 'preview' 和 'edit', 默认为 'preview' | ||
- platform 渲染器运行的平台,可选值为 'pc' 和 'mobile', 默认为 'pc' | ||
- width: 渲染器宽度,如果传入了 dom 参数,将会自动获取 dom 的宽度,否则默认为 300 | ||
- height: 渲染器高度,如果传入了 dom 参数,将会自动获取 dom 的高度,否则默认为 150 | ||
- autoResize: 是否自动调整渲染器大小,如果为 true,将会监听 dom 的 resize 事件,自动调整渲染器大小 | ||
- autoClear: 是否自动清除渲染器内容,如果为 true,将会在页面休眠时自动清楚内存中的渲染器内容,请注意在页面重新激活后重新渲染 | ||
- isShowModelInfo: 是否显示模型信息,如果为 true,将会在向场景中添加模型时显示对应的模型信息,如有多个模型将合并计算 | ||
initCommonAbility 方法提供了渲染器的基础能力,包括: | ||
initRenderer 初始化渲染器 | ||
initCamera 初始化相机 | ||
initScene 初始化场景 | ||
initLight 初始化灯光 | ||
initSign 初始化事件标记 | ||
- renderError 渲染错误事件 | ||
addCommonEventListener 添加渲染器的基础事件监听 | ||
- dom resize 事件 | ||
- 页面休眠事件,如果 autoClear 为 true,将会在页面休眠时自动清除渲染器内容 | ||
initEditAbility 方法提供了渲染器编辑模式的能力,包括: | ||
initEditCamera 初始化编辑模式下的相机 | ||
initSelectionBox 初始化选中辅助器 | ||
initViewHelper 初始化视角辅助器 | ||
initTransformControls 初始化变换控制器 | ||
initEditSign 初始化编辑模式下的事件标记 | ||
- selected 选中事件 | ||
- removeCurrSelectObject 移除当前选中的对象 | ||
- updateSelectModelParam 更新选中模型的参数 | ||
addEditEventListener 添加编辑模式下的事件监听 | ||
- keydown: delete 删除选中模型 | ||
- dblclick: focus | ||
- mousedown: 移动相机,滚轮放大 | ||
- touchstart: 移动相机,双指放大 | ||
initPreviewAbility 方法提供了渲染器预览模式的能力,包括: | ||
customUpdate 自定义更新方法,用于在渲染循环中执行自定义的更新操作 | ||
initAnimation 初始化动画与骨骼信息 | ||
getCurrMode 获取当前渲染器的模式 | ||
getCurrPlatform 获取当前渲染器的平台 | ||
changePerspectiveCamera 修改相机为透视相机 | ||
changeOrthographicCamera 修改相机为正交相机 | ||
addModel 向场景中添加模型 | ||
- model: 模型对象 | ||
- options: 模型配置项 | ||
- name: 模型名称 | ||
- id: 模型 id | ||
- modelRank: 'main', 'support', 'combination' 主体模型,辅助支撑模型,组合展示模型 | ||
- skeleton: 可以在添加模型时可以指定骨骼 | ||
- animations: 可以在添加模型时指定动画, 添加的动画,将保留和模型的索引关系 | ||
- [ | ||
{ | ||
name: '动画名称', | ||
animation: THREE.AnimationClip | ||
models: [] // 指定动画对应的模型,如果不指定,将会默认为所有模型 | ||
} | ||
] | ||
removeModel 从场景中移除模型, 同时释放所有资源与内存 | ||
clear: 场景销毁与内存清空, 移除所有模型,释放所有变量,取消事件注册,移除 dom 节点 | ||
*/ | ||
import * as THREE from "star_mod_custom_three"; | ||
@@ -12,7 +81,12 @@ import { Signal } from "./assets/signals.min.js"; | ||
AMBIENT_LIGHT_INTENSITY, | ||
RENDER_MODE_PREVIEW, | ||
RENDER_PLATFORM_PC, | ||
} from "./default.js"; | ||
import { | ||
getMode, | ||
getDom | ||
} from "./helper/renderHelper/base.helper.js" | ||
export class RenderBase { | ||
dom = null; | ||
mode = "editor"; | ||
mode = null; | ||
scene = null; | ||
@@ -28,33 +102,37 @@ camera = null; | ||
currSelectModel = null; | ||
options = { | ||
mode: RENDER_MODE_PREVIEW, | ||
platform: RENDER_PLATFORM_PC, | ||
width: 300, | ||
height: 150, | ||
autoResize: true, | ||
autoClear: true, | ||
isShowModelInfo: true, | ||
isShowSelectionBox: false, | ||
isShowSceneEnv: true, // 展示背景 | ||
isNeedScreenshot: false, | ||
}; | ||
// 渲染器初始化 | ||
constructor({ mode = "editor", dom }) { | ||
this.mode = this.getCurrMode(mode); | ||
this.dom = dom; | ||
// TODO: HACK 解决重复注入元素问题,原因待查 | ||
if (this.dom && this.dom.children && this.dom.children.length > 0) { | ||
// 对容器进行清空 | ||
this.dom.innerHTML = ""; | ||
constructor({ dom, options }) { | ||
if (options) { | ||
this.options = { | ||
...this.options, | ||
...options, | ||
} | ||
} | ||
if (dom) { | ||
this.dom = dom; | ||
} | ||
this.mode = getMode(this.options.mode); | ||
this.dom = getDom(this.dom); | ||
this.initCommonAbility(); | ||
this.initEditorAbility(); | ||
this.setAnimationLoop(() => { | ||
this.update(); | ||
}); | ||
this.mode.isEdit && this.initEditAbility(); | ||
this.mode.isPreview && this.initPreviewAbility(); | ||
} | ||
getCurrMode(mode) { | ||
if (mode === "editor") { | ||
return { | ||
isEditor: true, | ||
}; | ||
} | ||
if (mode === "render") { | ||
return { | ||
isRender: true, | ||
}; | ||
} | ||
} | ||
initCommonAbility() { | ||
@@ -66,4 +144,3 @@ // 渲染器初始化 | ||
// 场景初始化 | ||
this.scene = new THREE.Scene(); | ||
this.scene.background = new THREE.Color(SCENE_BACKGROUND_COLOR); | ||
this.initBaseScene(); | ||
// 灯光初始化 | ||
@@ -77,4 +154,18 @@ this.initLights(); | ||
initEditorAbility() { | ||
if (!this.mode.isEditor) { | ||
initRenderer() { | ||
this.renderer = new THREE.WebGLRenderer({ | ||
antialias: true, | ||
}); | ||
if (this.dom) { | ||
this.dom.append(this.renderer.domElement); | ||
} else { | ||
this.dom = document.createElement("div"); | ||
this.dom.style = "position: relative;"; | ||
this.dom.append(this.renderer.domElement); | ||
} | ||
} | ||
initEditAbility() { | ||
if (!this.mode.isEdit) { | ||
return; | ||
@@ -95,19 +186,19 @@ } | ||
this.initTransformControls(); | ||
// 编辑类事件注册 | ||
this.addEditorEventListener(); | ||
// 编辑状态事件注册 | ||
this.initEditSign(); | ||
// 编辑类事件监听 | ||
this.addEditEventListener(); | ||
} | ||
initRenderer() { | ||
this.renderer = new THREE.WebGLRenderer({ | ||
antialias: true, | ||
}); | ||
// TODO: 这里有点问题,之后再改 | ||
if (this.dom) { | ||
this.dom.append(this.renderer.domElement); | ||
} else { | ||
this.dom = document.createElement("div"); | ||
this.dom.style = "position: relative;"; | ||
this.dom.append(this.renderer.domElement); | ||
// TODO: 抛出预览时事件( 鼠标按下与抬起,监控 renderDom 不要监控 dom ) | ||
initPreviewAbility() { | ||
if (!this.mode.isPreview) { | ||
return; | ||
} | ||
if (this.options.isNeedScreenshot) { | ||
this.setAnimationLoop(() => { | ||
this.customUpdate(); | ||
}); | ||
} | ||
// this.initAnimation(); | ||
} | ||
@@ -120,74 +211,7 @@ | ||
changePerspectiveCamera() { | ||
this.camera = new THREE.PerspectiveCamera( | ||
45, | ||
this.dom.offsetWidth / this.dom.offsetHeight, | ||
0.1, | ||
10000 | ||
); | ||
if (this.cameraControls) { | ||
this.cameraControls.dispose(); | ||
} | ||
this.cameraControls = new EditorControls(this.camera, this.dom); | ||
this.camera.zoom = 1; | ||
this.viewHelper = new ViewHelper(this.camera, this.dom); | ||
this.viewHelper.controls = this.cameraControls; | ||
this.setSize(); | ||
initBaseScene() { | ||
this.scene = new THREE.Scene(); | ||
this.scene.background = new THREE.Color(SCENE_BACKGROUND_COLOR); | ||
} | ||
changeOrthographicCamera() { | ||
this.camera = new THREE.OrthographicCamera( | ||
-this.dom.offsetWidth, | ||
this.dom.offsetWidth, | ||
this.dom.offsetHeight, | ||
-this.dom.offsetHeight, | ||
1, | ||
100000 | ||
); | ||
if (this.cameraControls) { | ||
this.cameraControls.dispose(); | ||
} | ||
this.cameraControls = new EditorControls( | ||
this.camera, | ||
this.dom, | ||
"orthographic" | ||
); | ||
this.camera.zoom = 10; | ||
this.viewHelper = new ViewHelper(this.camera, this.dom); | ||
this.viewHelper.controls = this.cameraControls; | ||
this.setSize(); | ||
} | ||
initSignal() { | ||
this.signals = { | ||
selected: new Signal(), | ||
removeCurrSelectObject: new Signal(), | ||
updateSelectModelParam: new Signal(), | ||
renderError: new Signal(), | ||
}; | ||
} | ||
initViewHelper() { | ||
this.viewHelper = new ViewHelper(this.camera, this.dom); | ||
this.viewHelper.controls = this.cameraControls; | ||
const dom = document.createElement("div"); | ||
dom.style.position = "absolute"; | ||
dom.style.right = "0px"; | ||
dom.style.bottom = "0px"; | ||
dom.style.height = "128px"; | ||
dom.style.width = "128px"; | ||
this.dom.append(dom); | ||
} | ||
setSceneEnvironment(environment) { | ||
if (environment) { | ||
this.scene.background = environment; | ||
this.scene.environment = environment; | ||
} else { | ||
this.scene.background = new THREE.Color(SCENE_BACKGROUND_COLOR); | ||
this.scene.environment = null; | ||
} | ||
this.update(); | ||
} | ||
// 灯光初始化与设定 | ||
@@ -226,42 +250,70 @@ initLights() { | ||
changeAmbientLight({ color, intensity }) { | ||
if (color) { | ||
this.ambientLight.color.set(color); | ||
this.hemisphereLight.color.set(color); | ||
this.hemisphereLight.groundColor.set(color); | ||
initSelectionBox() { | ||
if (this.options.isShowSelectionBox) { | ||
this.box = new THREE.Box3(); | ||
this.selectionBox = new THREE.Box3Helper(this.box); | ||
this.selectionBox.material.depthTest = false; | ||
this.selectionBox.material.transparent = true; | ||
this.scene.add(this.selectionBox); | ||
} | ||
} | ||
if (intensity) { | ||
this.ambientLight.intensity = intensity; | ||
this.hemisphereLight.intensity = intensity; | ||
} | ||
initViewHelper() { | ||
this.viewHelper = new ViewHelper(this.camera, this.dom); | ||
this.viewHelper.controls = this.cameraControls; | ||
const dom = document.createElement("div"); | ||
dom.style.position = "absolute"; | ||
dom.style.right = "0px"; | ||
dom.style.bottom = "0px"; | ||
dom.style.height = "128px"; | ||
dom.style.width = "128px"; | ||
this.dom.append(dom); | ||
} | ||
changeDirectionalLights(light) { | ||
if (light.color) { | ||
this.directionalLights.forEach((item) => { | ||
item.color.set(light.color); | ||
}); | ||
} | ||
// 物体变换器加载 | ||
initTransformControls() { | ||
this.transformControls = new TransformControls(this.camera, this.dom); | ||
this.changeTransformControls("translate"); | ||
if (light.intensity) { | ||
this.directionalLights.forEach((item) => { | ||
item.intensity = light.intensity; | ||
}); | ||
} | ||
this.transformControls.addEventListener( | ||
"change", | ||
this.onTransformControlsChange.bind(this) | ||
); | ||
this.transformControls.addEventListener( | ||
"mouseDown", | ||
this.onTransformControlsMouseDown.bind(this) | ||
); | ||
this.transformControls.addEventListener( | ||
"mouseUp", | ||
this.onTransformControlsMouseUp.bind(this) | ||
); | ||
this.scene.add(this.transformControls); | ||
} | ||
setDirectionLightsPosition(light) { | ||
this.directionalLights[light.index].position.set(light.position); | ||
// TODO: 补充动画 | ||
initAnimation() { | ||
} | ||
initSelectionBox() { | ||
this.box = new THREE.Box3(); | ||
this.selectionBox = new THREE.Box3Helper(this.box); | ||
this.selectionBox.material.depthTest = false; | ||
this.selectionBox.material.transparent = true; | ||
this.selectionBox.visible = false; | ||
this.scene.add(this.selectionBox); | ||
// TODO: 页面与元素显隐 | ||
initSignal() { | ||
this.signals = { | ||
renderError: new Signal(), | ||
renderHidden: new Signal(), | ||
renderShow: new Signal(), | ||
}; | ||
} | ||
initEditSign() { | ||
this.signals = { | ||
...this.signals, | ||
selected: new Signal(), | ||
removeCurrSelectObject: new Signal(), | ||
updateSelectModelParam: new Signal(), | ||
} | ||
} | ||
addCommonEventListener() { | ||
@@ -273,6 +325,6 @@ // resize | ||
}); | ||
// TODO: 隐藏、关闭与显示 | ||
} | ||
// TODO: 编辑器注销后,事件没有移除 | ||
addEditorEventListener() { | ||
addEditEventListener() { | ||
// delete | ||
@@ -286,3 +338,3 @@ window.addEventListener("keydown", (event) => { | ||
// 单击 | ||
// 单击选中 | ||
this.dom.addEventListener("mousedown", (event) => { | ||
@@ -298,2 +350,3 @@ const array = this.getMousePosition( | ||
}); | ||
this.dom.addEventListener("touchstart", (event) => { | ||
@@ -332,23 +385,26 @@ const touch = event.changedTouches[0]; | ||
getMousePosition(dom, x, y) { | ||
if (dom) { | ||
const rect = dom.getBoundingClientRect(); | ||
return [(x - rect.left) / rect.width, (y - rect.top) / rect.height]; | ||
// 变形控制器监听 | ||
onTransformControlsChange() { | ||
const object = this.transformControls.object; | ||
if (object) { | ||
this.box.setFromObject(object, true); | ||
// 球体可以根据该事件对数值进行调整 | ||
this.signals.updateSelectModelParam.dispatch({ | ||
model: object, | ||
axis: this.transformControls.axis, | ||
mode: this.transformControls.getMode(), | ||
}); | ||
} | ||
this.update(); | ||
} | ||
getIntersects(point) { | ||
this.mouse.set(point.x * 2 - 1, -(point.y * 2) + 1); | ||
onTransformControlsMouseDown() { | ||
this.cameraControls.enabled = false; | ||
} | ||
this.raycaster.setFromCamera(this.mouse, this.camera); | ||
const objects = []; | ||
this.scene.traverseVisible(function (child) { | ||
objects.push(child); | ||
}); | ||
return this.raycaster.intersectObjects(objects, false); | ||
onTransformControlsMouseUp() { | ||
this.cameraControls.enabled = true; | ||
} | ||
// 鼠标事件 | ||
onMouseUp(event) { | ||
@@ -395,68 +451,110 @@ if (this.dom) { | ||
// 模型添加能力 | ||
addModel(model) { | ||
this.scene.add(model); | ||
// 获取各类属性 | ||
getRenderDom() { | ||
return this.renderer.domElement; | ||
} | ||
getCurrSelectModel() { | ||
return this.currSelectModel; | ||
} | ||
// 默认执行选中逻辑 | ||
this.selectModel(model); | ||
getMousePosition(dom, x, y) { | ||
if (dom) { | ||
const rect = dom.getBoundingClientRect(); | ||
return [(x - rect.left) / rect.width, (y - rect.top) / rect.height]; | ||
} | ||
} | ||
// 模型删除能力与内存释放 | ||
removeModel(model) { | ||
// 移除模型 | ||
model.parent && model.parent.remove(model); | ||
getIntersects(point) { | ||
this.mouse.set(point.x * 2 - 1, -(point.y * 2) + 1); | ||
// TODO: 移除材质 | ||
if (model.children) { | ||
model.traverse((child) => { | ||
if (child.geometry) { | ||
child.geometry.dispose(); | ||
} | ||
if (child.material && !Array.isArray(child.material)) { | ||
child.material.dispose(); | ||
} | ||
}); | ||
this.raycaster.setFromCamera(this.mouse, this.camera); | ||
const objects = []; | ||
this.scene.traverseVisible(function (child) { | ||
objects.push(child); | ||
}); | ||
return this.raycaster.intersectObjects(objects, false); | ||
} | ||
// 设置各类内容 | ||
setSize(width, height) { | ||
if (!this.dom) { | ||
return; | ||
} | ||
if (width && height) { | ||
this.dom.width = width; | ||
this.dom.height = height; | ||
} | ||
let assembleWidth = 0; | ||
if (this.dom.style.width) { | ||
assembleWidth = width || parseFloat(this.dom.style.width); | ||
} else { | ||
if (model.geometry) { | ||
model.geometry.dispose(); | ||
} | ||
assembleWidth = width || this.dom.offsetWidth; | ||
} | ||
// 取消选中 | ||
this.selectModel(null); | ||
let assembleHeight = 0; | ||
if (this.dom.style.height) { | ||
assembleHeight = height || parseFloat(this.dom.style.height); | ||
} else { | ||
assembleHeight = height || this.dom.offsetHeight; | ||
} | ||
this.updateAspectRatio(assembleWidth, assembleHeight); | ||
// 这里本身做了防抖 | ||
this.renderer.setSize(assembleWidth, assembleHeight); | ||
this.renderer.setViewport(0, 0, assembleWidth, assembleHeight); | ||
} | ||
// 物体变换器加载 | ||
initTransformControls() { | ||
this.transformControls = new TransformControls(this.camera, this.dom); | ||
this.changeTransformControls("translate"); | ||
// TODO: 设置相机动画 | ||
setPreviewData(previewData) { | ||
if (previewData.cameraPosition) { | ||
this.camera.position.set( | ||
previewData.cameraPosition.x, | ||
previewData.cameraPosition.y, | ||
previewData.cameraPosition.z | ||
); | ||
} | ||
this.transformControls.addEventListener( | ||
"change", | ||
this.onTransformControlsChange.bind(this) | ||
); | ||
if (previewData.previewCenter) { | ||
this.camera.lookAt(previewData.previewCenter); | ||
} | ||
this.transformControls.addEventListener( | ||
"mouseDown", | ||
this.onTransformControlsMouseDown.bind(this) | ||
); | ||
if (previewData.ambientLight) { | ||
this.changeAmbientLight({ | ||
color: previewData.ambientLight.color, | ||
intensity: previewData.ambientLight.intensity, | ||
}); | ||
} | ||
this.transformControls.addEventListener( | ||
"mouseUp", | ||
this.onTransformControlsMouseUp.bind(this) | ||
); | ||
if (previewData.directionalLight) { | ||
this.changeDirectionalLights({ | ||
color: previewData.directionalLight[0].color, | ||
intensity: previewData.directionalLight[0].intensity, | ||
}); | ||
} | ||
} | ||
this.scene.add(this.transformControls); | ||
// 暂停与播放 | ||
setAnimationLoop(callback) { | ||
if (this.renderer) { | ||
this.renderer.setAnimationLoop(callback); | ||
} | ||
} | ||
onTransformControlsChange() { | ||
const object = this.transformControls.object; | ||
if (object) { | ||
this.box.setFromObject(object, true); | ||
// 球体可以根据该事件对数值进行调整 | ||
this.signals.updateSelectModelParam.dispatch({ | ||
model: object, | ||
axis: this.transformControls.axis, | ||
mode: this.transformControls.getMode(), | ||
}); | ||
setDirectionLightsPosition(light) { | ||
this.directionalLights[light.index].position.set(light.position); | ||
} | ||
setSceneEnvironment(environment) { | ||
if (environment) { | ||
this.scene.background = environment; | ||
this.scene.environment = environment; | ||
} else { | ||
this.scene.background = new THREE.Color(SCENE_BACKGROUND_COLOR); | ||
this.scene.environment = null; | ||
} | ||
@@ -466,10 +564,110 @@ this.update(); | ||
onTransformControlsMouseDown() { | ||
this.cameraControls.enabled = false; | ||
// 创建各类变量 | ||
// 创建网格 | ||
createGrid(size, divisions, color) { | ||
const grid = new THREE.GridHelper(size, divisions, color); | ||
grid.isGrid = true; | ||
grid.material.color.setHex(color); | ||
grid.material.vertexColors = false; | ||
grid.visible = false; | ||
grid.traverse((child) => { | ||
child.visible = false; | ||
}); | ||
this.grids.push(grid); | ||
this.addModel(grid); | ||
} | ||
onTransformControlsMouseUp() { | ||
this.cameraControls.enabled = true; | ||
changeGirdVisible() { | ||
this.grids.forEach((item) => { | ||
item.visible = !item.visible; | ||
}); | ||
this.update(); | ||
} | ||
// 这里扩展三种模式 | ||
changeTransformControls(mode) { | ||
// 切换转换器模式 | ||
this.transformControls.setMode(mode); | ||
} | ||
changeAmbientLight({ color, intensity }) { | ||
if (color) { | ||
this.ambientLight.color.set(color); | ||
this.hemisphereLight.color.set(color); | ||
this.hemisphereLight.groundColor.set(color); | ||
} | ||
if (intensity) { | ||
this.ambientLight.intensity = intensity; | ||
this.hemisphereLight.intensity = intensity; | ||
} | ||
} | ||
changeDirectionalLights(light) { | ||
if (light.color) { | ||
this.directionalLights.forEach((item) => { | ||
item.color.set(light.color); | ||
}); | ||
} | ||
if (light.intensity) { | ||
this.directionalLights.forEach((item) => { | ||
item.intensity = light.intensity; | ||
}); | ||
} | ||
} | ||
// 创建透视相机 | ||
changePerspectiveCamera() { | ||
this.camera = new THREE.PerspectiveCamera( | ||
45, | ||
this.dom.offsetWidth / this.dom.offsetHeight, | ||
0.1, | ||
10000 | ||
); | ||
if (this.cameraControls) { | ||
this.cameraControls.dispose(); | ||
} | ||
this.cameraControls = new EditorControls(this.camera, this.dom); | ||
this.camera.zoom = 1; | ||
if (this.mode.isEdit) { | ||
this.viewHelper = new ViewHelper(this.camera, this.dom); | ||
this.viewHelper.controls = this.cameraControls; | ||
} | ||
this.setSize(); | ||
} | ||
// 创建正交相机 TODO: 正交相机 focus | ||
changeOrthographicCamera() { | ||
this.camera = new THREE.OrthographicCamera( | ||
-this.dom.offsetWidth, | ||
this.dom.offsetWidth, | ||
this.dom.offsetHeight, | ||
-this.dom.offsetHeight, | ||
1, | ||
100000 | ||
); | ||
if (this.cameraControls) { | ||
this.cameraControls.dispose(); | ||
} | ||
this.cameraControls = new EditorControls( | ||
this.camera, | ||
this.dom, | ||
"orthographic" | ||
); | ||
this.camera.zoom = 10; | ||
if (this.mode.isEdit) { | ||
this.viewHelper = new ViewHelper(this.camera, this.dom); | ||
this.viewHelper.controls = this.cameraControls; | ||
} | ||
this.setSize(); | ||
} | ||
// resize 能力 | ||
updateAspectRatio(width, height) { | ||
this.camera.aspect = width / height; | ||
this.camera.updateProjectionMatrix(); | ||
} | ||
removeTransformControls() { | ||
@@ -500,8 +698,2 @@ this.scene.remove(this.transformControls); | ||
// 这里扩展三种模式 | ||
changeTransformControls(mode) { | ||
// 切换转换器模式 | ||
this.transformControls.setMode(mode); | ||
} | ||
intersectionsDetected(intersects) { | ||
@@ -547,7 +739,2 @@ if (intersects.length > 0) { | ||
// 模型数据纰漏能力 | ||
getCurrSelectModel() { | ||
return this.currSelectModel; | ||
} | ||
// 双击居中能力 | ||
@@ -567,52 +754,37 @@ focusModel(target) { | ||
// 创建网格 | ||
createGrid(size, divisions, color) { | ||
const grid = new THREE.GridHelper(size, divisions, color); | ||
grid.isGrid = true; | ||
grid.material.color.setHex(color); | ||
grid.material.vertexColors = false; | ||
grid.visible = false; | ||
grid.traverse((child) => { | ||
child.visible = false; | ||
}); | ||
// 模型添加能力 | ||
addModel(model, options) { | ||
if (options) { | ||
// TODO: 根据 options.modelRank 类别计算展示面数 | ||
} | ||
this.scene.add(model); | ||
this.grids.push(grid); | ||
this.addModel(grid); | ||
// 默认执行选中逻辑 | ||
this.selectModel(model); | ||
} | ||
changeGirdVisible() { | ||
this.grids.forEach((item) => { | ||
item.visible = !item.visible; | ||
}); | ||
this.update(); | ||
} | ||
// 模型删除能力与内存释放 | ||
removeModel(model) { | ||
// 移除模型 | ||
model.parent && model.parent.remove(model); | ||
// resize 能力 | ||
updateAspectRatio(width, height) { | ||
this.camera.aspect = width / height; | ||
this.camera.updateProjectionMatrix(); | ||
} | ||
setSize(width, height) { | ||
if (!this.dom) { | ||
return; | ||
if (model.children) { | ||
model.traverse((child) => { | ||
if (child.geometry) { | ||
child.geometry.dispose(); | ||
} | ||
if (child.material && !Array.isArray(child.material)) { | ||
child.material.dispose(); | ||
} | ||
}); | ||
} else { | ||
if (model.geometry) { | ||
model.geometry.dispose(); | ||
} | ||
} | ||
if (width && height) { | ||
this.dom.width = width; | ||
this.dom.height = height; | ||
} | ||
const assembleWidth = width || this.dom.offsetWidth; | ||
const assembleHeight = height || this.dom.offsetHeight; | ||
this.updateAspectRatio(assembleWidth, assembleHeight); | ||
this.renderer.setSize(assembleWidth, assembleHeight); | ||
this.renderer.setViewport(0, 0, assembleWidth, assembleHeight); | ||
// 取消选中 | ||
this.selectModel(null); | ||
} | ||
// 暂停与播放 | ||
setAnimationLoop(callback) { | ||
this.renderer.setAnimationLoop(callback); | ||
} | ||
update() { | ||
@@ -634,24 +806,32 @@ if (this.renderer) { | ||
getRenderDom() { | ||
return this.renderer.domElement; | ||
// preview 模式特供 update 用于截取当前帧数据 | ||
customUpdate() { | ||
if (this.renderer) { | ||
this.update(); | ||
if (this.mixer !== null) { | ||
// this.mixer.update(this.clock.getDelta()); | ||
} | ||
if (this.generateCurrFrameFlag) { | ||
const img = new Image(); | ||
let imgData = this.renderer.domElement.toDataURL("image/jpeg"); | ||
img.src = imgData; | ||
this.currFrame = img; | ||
this.generateCurrFrameFlag = false; | ||
} | ||
} | ||
} | ||
// 场景销毁与内存清空 | ||
clear() { | ||
// 删除所有模型与相关内存 | ||
this.scene.children.forEach((item) => { | ||
this.removeModel(item); | ||
}); | ||
this.renderer.dispose(); | ||
this.camera = null; | ||
this.scene = null; | ||
this.renderer = null; | ||
this.viewHelper = null; | ||
this.dom = null; | ||
this.cameraControls = null; | ||
this.transformControls = null; | ||
this.ambientLight = null; | ||
this.directionalLights = []; | ||
this.signals = null; | ||
this.currSelectModel = null; | ||
this.scene.background = new THREE.Color(SCENE_BACKGROUND_COLOR); | ||
this.scene.environment = null; | ||
this.dom.innerHTML = ""; | ||
// 移除并清空 dom | ||
// 注销 render | ||
} | ||
} |
491
render.js
@@ -1,285 +0,296 @@ | ||
import * as THREE from "star_mod_custom_three"; | ||
/** | ||
渲染器 preview 模式应用 | ||
实例化时可配置所选项 --> 在 renderAuxiliary 中写定 | ||
根据需求选择使用那种资源添加方式 | ||
setMainModel / setCombinationModels / setFashionAnimations | ||
在执行 start 之后通过 render.helper.js 中的 ChannelMaterialAuxiliary 对通道中的纹理文件进行加载 | ||
根据不同的模式激活是否完成加载选项 | ||
在对外抛出加载完成前,根据配置选择是否展示 model info, 并提供显隐开关 | ||
所有辅助性资源: | ||
1. 地板 | ||
2. 头发 | ||
3. 头 | ||
4. 身体 | ||
constructor | ||
- dom | ||
- options | ||
- mode: 固定为 preview | ||
- platform: pc | mobile | ||
- width | ||
- height | ||
- autoResize | ||
- autoClear | ||
- isShowModelInfo | ||
setMainModel / setCombinationModels / setFashionAnimations | ||
- setMainModel | ||
- fbx | ||
- channelMaterial | ||
- auxiliaryType | ||
- setCombinationModels | ||
- models | ||
- fbx | ||
- channelMaterial | ||
- auxiliaryType | ||
- animations 直接获取? | ||
- setFashionAnimations | ||
- animations | ||
- auxiliaryType | ||
start | ||
sign | ||
- loadCompleted 所有资源加载完成可以渲染内容 | ||
- 开始展示,并添加 model info | ||
initAnimations 在加载完成后,如果存在动画选项的话 | ||
playAnimations | ||
stopAnimations | ||
initAuxiliaryModels 查找辅助模型并一同添加, 保留索引以切换显隐 | ||
tga 文件渲染 | ||
*/ | ||
// import * as THREE from "star_mod_custom_three"; | ||
import TgaLoader from "tga-js"; | ||
import { RenderBase } from "./render.base"; | ||
import { | ||
getEnvMapList, | ||
getEnvMapByName, | ||
defaultModel, | ||
defaultModelAnimation, | ||
FEMALE_HAIR_KEY, | ||
MALE_HAIR_KEY, | ||
FEMALE_HEAD_MATERIAL_KEY, | ||
MALE_HEAD_MATERIAL_KEY, | ||
// defaultModel, | ||
// defaultModelAnimation, | ||
// FEMALE_HAIR_KEY, | ||
// MALE_HAIR_KEY, | ||
// FEMALE_HEAD_MATERIAL_KEY, | ||
// MALE_HEAD_MATERIAL_KEY, | ||
RENDER_MODE_PREVIEW | ||
} from "./default"; | ||
import { ModelProcessor } from "./helper/modelHelper/index"; | ||
import { calculateVerticesAndFace } from "./helper/calculate"; | ||
import { loadFile } from "./helper/filesHelper"; | ||
// import { calculateVerticesAndFace } from "./helper/calculate"; | ||
// import { loadFile } from "./helper/filesHelper"; | ||
import { | ||
MainModelAuxiliary, // 主模型辅助类 | ||
channelMaterialParams, | ||
ChannelMaterialAuxiliary, // 材质通道辅助类 | ||
} from "./helper/renderHelper/render.helper"; | ||
import { | ||
PreviewDataHelper, | ||
} from "@/renderer/render/helper/parameterHelper"; | ||
import { | ||
getModelMaterialChannelLength, | ||
} from "@/renderer/render/helper/parser"; | ||
export class Render extends RenderBase { | ||
generateCurrFrameFlag = false; | ||
currFrame = null; | ||
modelRender = null; | ||
modelRenderList = []; | ||
combinationModelGroup = null; | ||
clock = new THREE.Clock(); | ||
mixer = null; | ||
mainModels = []; // 所有模型 | ||
modelsLoader = []; // 请求列表,在 start 时同时执行所有请求内容 | ||
/** | ||
* 所有可能存在的组合: | ||
* 上传单个男/女性头发: 增加男/女头部模型,增加默认男/女性身体,添加男/女性默认动画, 添加地板 | ||
* 上传单个男/女性身体: 增加男/女头部模型,增加默认男/女性头发,添加男/女性默认动画,添加地板 | ||
* 上传组合男/女时装: 增加男/女头部模型,添加男/女性默认动画,添加地板 | ||
* 上传单个普通单品: 增加地板 | ||
* 上传个性动画: 增加男/女头部模型,增加默认男/女性头发,增加默认男/女性身体,添加男/女性默认动画, 添加地板 | ||
*/ | ||
auxiliaryModels = { | ||
floor: null, // 地板 | ||
maleHair: null, // 男性时装头发 | ||
maleHeader: null, // 男性头部 | ||
maleBody: null, // 男性时装身体 | ||
femaleHair: null, // 女性时装头发 | ||
femaleHeader: null, // 女性头部 | ||
femaleBody: null, // 女性时装身体 | ||
}; | ||
previewData = null; // 预览数据 | ||
animations = []; // 所有动画: 从配置中请求获取: | ||
constructor( | ||
{ container, platform, isShowScene } = { | ||
container: null, | ||
platform: "pc", | ||
isShowScene: true, | ||
} | ||
) { | ||
super({ mode: "render", dom: container }); | ||
this.setAnimationLoop(() => { | ||
this.customUpdate(); | ||
}); | ||
let processEnv = "domestic"; | ||
try { | ||
if (platform === "pc") { | ||
processEnv = process.env.VUE_APP_NAME; | ||
} else if (platform === "mobile") { | ||
processEnv = import.meta.env.VITE_APP_NAME; | ||
} | ||
} catch (e) { | ||
console.error(e); | ||
processEnv = "domestic"; | ||
} | ||
if (isShowScene) { | ||
getEnvMapByName("sky", processEnv).then((env) => { | ||
this.setSceneEnvironment(env.data); | ||
}); | ||
} | ||
} | ||
constructor({dom, options}) { | ||
super({dom, options: { | ||
...options, | ||
mode: RENDER_MODE_PREVIEW | ||
} }); | ||
setRenderModel(model, isShowModelInfo = true) { | ||
// 计算设置模型 | ||
const modelRender = new ModelProcessor(model); | ||
// 将模型添加到场景 | ||
this.addModel(modelRender.getModel()); | ||
this.focusModel(modelRender.getModel()); | ||
// 旋转相机角度 | ||
// 设置灯光参数 | ||
const size = modelRender.getModelSize(); | ||
// temp | ||
const max = Math.max(size.x, size.y, size.z); | ||
const min = Math.min(size.x, size.y, size.z); | ||
if (max > 400) { | ||
this.signals.renderError.dispatch(); | ||
} | ||
if (min < 10) { | ||
this.signals.renderError.dispatch(); | ||
} | ||
this.directionalLights[0].position.set(max * 2, max * 2, max * 2); | ||
this.directionalLights[1].position.set(max * -2, max * -2, max * -2); | ||
this.directionalLights[2].position.set(0, max * -2, max * 2); | ||
this.modelRender = modelRender; | ||
if (isShowModelInfo) { | ||
this.handleRenderModelInfo(); | ||
} | ||
this.initRenderSign(); | ||
} | ||
setCombinationRenderModel(list) { | ||
const group = new THREE.Group(); | ||
const modelRenderList = []; | ||
list.forEach((model) => { | ||
const modelRender = new ModelProcessor(model); | ||
group.add(modelRender.getModel()); | ||
modelRenderList.push(modelRender); | ||
}); | ||
this.combinationModelGroup = group; | ||
this.addModel(group); | ||
this.focusModel(group); | ||
this.modelRenderList = modelRenderList; | ||
} | ||
async setAuxiliaryModel(key) { | ||
if (key === MALE_HAIR_KEY) { | ||
this.maleHeadModel = await this.createMaleHeadModel(); | ||
initSceneEnv() { | ||
if (this.options.isShowSceneEnv) { | ||
// 根据 platform 加载默认天空盒 | ||
} | ||
// 女头 | ||
if (key === FEMALE_HAIR_KEY) { | ||
this.femaleHeadModel = await this.createFemaleHeadModel(); | ||
} | ||
} | ||
async createMaleHeadModel() { | ||
const meshUrl = defaultModel[MALE_HAIR_KEY].fbx.domesticPath; | ||
const mesh = await loadFile({ | ||
url: meshUrl, | ||
type: "fbx", | ||
}); | ||
const modelRender = new ModelProcessor(mesh); | ||
modelRender.createSpecificMaterial(MALE_HEAD_MATERIAL_KEY); | ||
this.maleHeadModel = modelRender.getModel(); | ||
this.maleHeadModel.visible = true; | ||
this.addModel(this.maleHeadModel); | ||
return this.maleHeadModel; | ||
} | ||
initRenderSign() { | ||
changeMaleHeadModelVisible() { | ||
this.maleHeadModel.visible = !this.maleHeadModel.visible; | ||
} | ||
async createFemaleHeadModel() { | ||
const meshUrl = defaultModel[FEMALE_HAIR_KEY].fbx.domesticPath; | ||
const mesh = await loadFile({ | ||
url: meshUrl, | ||
type: "fbx", | ||
}); | ||
const modelRender = new ModelProcessor(mesh); | ||
/** | ||
* 1. 模型需要主动传入,因为有加密和非加密的情况,需要在外面获取后加载后传入 | ||
* 2. previewData 直接传入可以在 render 内转换 | ||
* 3. auxiliaryType 传入后获取对应辅助模型 | ||
* 4. 纹理文件整理后按照 store 的形式传入加载 | ||
* 5. 辅助模型与纹理文件加载完成之后 | ||
*/ | ||
async setMainModel({ | ||
mainModel, | ||
mainModelLocalCache, | ||
mainModelData, | ||
mainMaterialMap, | ||
extraMaterialList, | ||
// goodSecondaryClassification, | ||
// fashionConfig, | ||
previewData, | ||
}) { | ||
// mainModelHelper | ||
const mainModelAuxiliary = new MainModelAuxiliary( | ||
mainModel, | ||
mainModelLocalCache, | ||
mainModelData | ||
); | ||
const modelOriginalData = await mainModelAuxiliary.loadModel(); | ||
const modelRender = new ModelProcessor(modelOriginalData); | ||
// channelMaterialHelper | ||
const channelMaterialAuxiliary = new ChannelMaterialAuxiliary([]); | ||
modelRender.createSpecificMaterial(FEMALE_HEAD_MATERIAL_KEY); | ||
this.femaleHeadModel = modelRender.getModel(); | ||
this.femaleHeadModel.visible = true; | ||
this.addModel(this.femaleHeadModel); | ||
return this.femaleHeadModel; | ||
} | ||
// 所有通道纹理全部加载完成并设置对应材质后返回成功 | ||
const loadModelChannelTexture = (model) => { | ||
return new Promise((resolve) => { | ||
mainModelAuxiliary.setMainModelData(model); | ||
this.mainModels.push(modelRender); | ||
changeFemaleHeadModelVisible() { | ||
this.femaleHeadModel.visible = !this.femaleHeadModel.visible; | ||
} | ||
const channelTextureLoadSuccess = (textureList) => { | ||
const loaderList = []; | ||
return new Promise((resolve) => { | ||
// 更新模型材质类型 | ||
for (let index = 0; index < channelMaterialAuxiliary.channelMaterialParamsList.length; index++) { | ||
const item = channelMaterialAuxiliary.channelMaterialParamsList[index]; | ||
loaderList.push(modelRender.updateModelMaterialType(index, item.materialType)); | ||
} | ||
Promise.all(loaderList).then((res) => { | ||
for(let i = 0; i < textureList.length; i++) { | ||
const texture = textureList[i]; | ||
for (let j = 0; j < texture.length; j++) { | ||
const { index, key, res } = texture[j]; | ||
channelMaterialAuxiliary.setChannelItemTextureData(index, key, res); | ||
// 更新材质与材质列表 | ||
modelRender.updateModelMaterialParam(index, channelMaterialAuxiliary.getChannelItem(index)); | ||
modelRender.updateModelMaterialTexture(index, channelMaterialAuxiliary.getChannelItemTextureData(index)); | ||
} | ||
} | ||
resolve(res); | ||
}); | ||
}); | ||
}; | ||
stopPlayAnimation() { | ||
this.animationAction.stop(); | ||
} | ||
channelMaterialAuxiliary.initChannelMaterialParamsList( | ||
getModelMaterialChannelLength(model), | ||
channelMaterialParams, | ||
); | ||
setAnimationList(list) { | ||
const bones = | ||
this.combinationModelGroup.children[1].children[0].clone(true); | ||
if (this.combinationModelGroup.children[0].children[0].type === "Bone") { | ||
this.combinationModelGroup.children[0].add(bones); | ||
} | ||
this.animations = list; | ||
} | ||
channelMaterialAuxiliary.setMaterialParamsList(mainMaterialMap, extraMaterialList).then(res => { | ||
channelTextureLoadSuccess(res).then(() => { | ||
resolve() | ||
}); | ||
}); | ||
}); | ||
}; | ||
playAnimation(index) { | ||
const flock = new THREE.AnimationObjectGroup( | ||
this.combinationModelGroup.children[0], | ||
this.maleHeadModel, | ||
this.combinationModelGroup.children[1] | ||
// 调用 start 时开始同时加载所有内容 | ||
this.modelsLoader.push( | ||
loadModelChannelTexture(modelOriginalData) | ||
); | ||
this.mixer = new THREE.AnimationMixer(flock); | ||
this.animationAction = this.mixer.clipAction(this.animations[index]); | ||
this.animationAction.play(); | ||
} | ||
// TODO: 补充动画播放 | ||
async playDefaultAnimation(key) { | ||
if (defaultModelAnimation[key]) { | ||
const url = defaultModelAnimation[key].dancing.domesticPath; | ||
const file = await loadFile({ | ||
url, | ||
type: "fbx", | ||
// 设置预览数据并请求天空盒资源 | ||
const previewHelper = new PreviewDataHelper(); | ||
this.previewData = previewHelper.transPreviewDataToRender(previewData); | ||
const getCurrEnvMap = () => { | ||
const envMap = this.previewData.envMap || 'sky'; | ||
return new Promise((resolve) => { | ||
getEnvMapByName(envMap, process.env.VUE_APP_NAME).then((environment) => { | ||
if (environment) { | ||
this.setSceneEnvironment(environment.data); | ||
modelRender.setModelMaterialEnvMap(environment.data); | ||
} | ||
resolve(); | ||
}); | ||
}); | ||
const modelRender = new ModelProcessor(file); | ||
const bones = | ||
this.combinationModelGroup.children[1].children[0].clone(true); | ||
if (this.combinationModelGroup.children[0].children[0].type === "Bone") { | ||
this.combinationModelGroup.children[0].add(bones); | ||
} | ||
const flock = new THREE.AnimationObjectGroup( | ||
this.combinationModelGroup.children[0], | ||
this.maleHeadModel, | ||
this.combinationModelGroup.children[1] | ||
); | ||
this.mixer = new THREE.AnimationMixer(flock); | ||
this.animationAction = this.mixer.clipAction( | ||
modelRender.getModel().animations[0] | ||
); | ||
} | ||
this.modelsLoader.push( | ||
getCurrEnvMap() | ||
); | ||
this.animationAction.play(); | ||
} | ||
// 根据 goodSecondaryClassification 与 fashionConfig 请求对应辅助模型的数据 | ||
} | ||
handleRenderModelInfo() { | ||
const mainObject = this.modelRender; | ||
const { faces, vertices } = calculateVerticesAndFace(mainObject.model); | ||
const size = mainObject.getModelSize(); | ||
const div = document.createElement("div"); | ||
div.innerHTML = ` | ||
vertices: ${vertices}<br> | ||
triangles: ${faces}<br> | ||
size: ${parseInt(size.x)}cm * ${parseInt(size.y)}cm * ${parseInt( | ||
size.z | ||
)}cm | ||
`; | ||
div.style.position = "absolute"; | ||
div.style.bottom = "6px"; | ||
div.style.left = "6px"; | ||
div.style.color = "#fff"; | ||
div.style.fontSize = "12px"; | ||
div.style.zIndex = "100"; | ||
div.style.transform = "scale(0.8)"; | ||
div.style.transformOrigin = "left bottom"; | ||
div.style.textAlign = "left"; | ||
setCombinationModels() { | ||
this.dom.appendChild(div); | ||
} | ||
setRenderPreviewData(previewData) { | ||
if (previewData.cameraPosition) { | ||
this.camera.position.set( | ||
previewData.cameraPosition.x, | ||
previewData.cameraPosition.y, | ||
previewData.cameraPosition.z | ||
); | ||
} | ||
setFashionAnimations() { | ||
if (previewData.previewCenter) { | ||
this.camera.lookAt(previewData.previewCenter); | ||
} | ||
} | ||
if (previewData.ambientLight) { | ||
this.changeAmbientLight({ | ||
color: previewData.ambientLight.color, | ||
intensity: previewData.ambientLight.intensity, | ||
// 加载完成后可以请求其它的天空盒资源 | ||
getAllEnvMap() { | ||
return new Promise((resolve) => { | ||
getEnvMapList().then((envMapList) => { | ||
resolve(envMapList); | ||
}); | ||
} | ||
}); | ||
} | ||
if (previewData.directionalLight) { | ||
this.changeDirectionalLights({ | ||
color: previewData.directionalLight[0].color, | ||
intensity: previewData.directionalLight[0].intensity, | ||
start() { | ||
return new Promise((resolve) => { | ||
// promise 方法,监听所有资源已经加载完成,可以渲染,将所有物品添加到场景当中,执行相机动画,外面可以在 await start 之后切换加载动画的显隐 | ||
Promise.all(this.modelsLoader).then(() => { | ||
// 将所有模型添加到场景当中 | ||
this.mainModels.forEach((modelRender) => { | ||
const model = modelRender.getModel(); | ||
this.addModel(model); | ||
// 如果有 preview 的话就根据 previewData 执行相机动画,如果没有的话直接 focusModel | ||
this.focusModel(model); | ||
}); | ||
this.setPreviewData(this.previewData); | ||
resolve(); | ||
}); | ||
} | ||
}); | ||
if (previewData.envMap) { | ||
this.setSceneEnvironment(previewData.envMap.data); | ||
} | ||
} | ||
customUpdate() { | ||
if (this.renderer) { | ||
this.renderer.setViewport( | ||
0, | ||
0, | ||
this.dom.offsetWidth, | ||
this.dom.offsetHeight | ||
); | ||
this.renderer.render(this.scene, this.camera); | ||
if (this.mixer !== null) { | ||
//clock.getDelta()方法获得两帧的时间间隔 | ||
// 更新混合器相关的时间 | ||
this.mixer.update(this.clock.getDelta()); | ||
} | ||
if (this.generateCurrFrameFlag) { | ||
const img = new Image(); | ||
let imgData = this.renderer.domElement.toDataURL("image/jpeg"); | ||
console.log("this.renderer"); | ||
img.src = imgData; | ||
// 如果是时装或个性动画类内容则播放默认动画 | ||
this.currFrame = img; | ||
this.generateCurrFrameFlag = false; | ||
} | ||
} | ||
} | ||
// 场景绘制 canvas 生成图片 | ||
getCurrPreviewStoreData() { | ||
const previewHelper = new PreviewDataHelper(); | ||
const previewData = { | ||
cameraPosition: { | ||
x: this.camera.position.x, | ||
y: this.camera.position.y, | ||
z: this.camera.position.z, | ||
}, | ||
previewCenter: { | ||
x: this.cameraControls.center.x, | ||
y: this.cameraControls.center.y, | ||
z: this.cameraControls.center.z, | ||
}, | ||
ambientLight: this.ambientLight, | ||
directionalLight: this.directionalLights, | ||
}; | ||
return previewHelper.transPreviewDataToStore(previewData); | ||
} | ||
getCurrFrame() { | ||
return new Promise((resolve) => { | ||
this.generateCurrFrameFlag = true; | ||
console.log("this.generateCurrFrameFlag", this.generateCurrFrameFlag); | ||
setTimeout(() => { | ||
@@ -295,3 +306,3 @@ resolve(this.currFrame); | ||
const tga = new TgaLoader(); | ||
tga.open(url, () => { | ||
tga.open(url, () => { | ||
const dom = tga.getCanvas(); | ||
@@ -298,0 +309,0 @@ const img = new Image(); |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
356148
33
9808
22
3