[3d] initial version of 3d viewer (#3968)

Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
Terry Jia
2025-08-11 00:09:19 -04:00
committed by GitHub
parent 8e357c41e3
commit d22d62b670
46 changed files with 2071 additions and 42 deletions

View File

@@ -179,12 +179,16 @@ export class CameraManager implements CameraManagerInterface {
}
handleResize(width: number, height: number): void {
const aspect = width / height
this.updateAspectRatio(aspect)
}
updateAspectRatio(aspect: number): void {
if (this.activeCamera === this.perspectiveCamera) {
this.perspectiveCamera.aspect = width / height
this.perspectiveCamera.aspect = aspect
this.perspectiveCamera.updateProjectionMatrix()
} else {
const frustumSize = 10
const aspect = width / height
this.orthographicCamera.left = (-frustumSize * aspect) / 2
this.orthographicCamera.right = (frustumSize * aspect) / 2
this.orthographicCamera.top = frustumSize / 2

View File

@@ -9,11 +9,11 @@ import { EventManager } from './EventManager'
import { LightingManager } from './LightingManager'
import { LoaderManager } from './LoaderManager'
import { ModelExporter } from './ModelExporter'
import { ModelManager } from './ModelManager'
import { NodeStorage } from './NodeStorage'
import { PreviewManager } from './PreviewManager'
import { RecordingManager } from './RecordingManager'
import { SceneManager } from './SceneManager'
import { SceneModelManager } from './SceneModelManager'
import { ViewHelperManager } from './ViewHelperManager'
import {
CameraState,
@@ -29,22 +29,28 @@ class Load3d {
protected animationFrameId: number | null = null
node: LGraphNode
protected eventManager: EventManager
protected nodeStorage: NodeStorage
protected sceneManager: SceneManager
protected cameraManager: CameraManager
protected controlsManager: ControlsManager
protected lightingManager: LightingManager
protected viewHelperManager: ViewHelperManager
protected previewManager: PreviewManager
protected loaderManager: LoaderManager
protected modelManager: ModelManager
protected recordingManager: RecordingManager
eventManager: EventManager
nodeStorage: NodeStorage
sceneManager: SceneManager
cameraManager: CameraManager
controlsManager: ControlsManager
lightingManager: LightingManager
viewHelperManager: ViewHelperManager
previewManager: PreviewManager
loaderManager: LoaderManager
modelManager: SceneModelManager
recordingManager: RecordingManager
STATUS_MOUSE_ON_NODE: boolean
STATUS_MOUSE_ON_SCENE: boolean
STATUS_MOUSE_ON_VIEWER: boolean
INITIAL_RENDER_DONE: boolean = false
targetWidth: number = 512
targetHeight: number = 512
targetAspectRatio: number = 1
isViewerMode: boolean = false
constructor(
container: Element | HTMLElement,
options: Load3DOptions = {
@@ -54,6 +60,16 @@ class Load3d {
) {
this.node = options.node || ({} as LGraphNode)
this.clock = new THREE.Clock()
this.isViewerMode = options.isViewerMode || false
const widthWidget = this.node.widgets?.find((w) => w.name === 'width')
const heightWidget = this.node.widgets?.find((w) => w.name === 'height')
if (widthWidget && heightWidget) {
this.targetWidth = widthWidget.value as number
this.targetHeight = heightWidget.value as number
this.targetAspectRatio = this.targetWidth / this.targetHeight
}
this.renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true })
this.renderer.setSize(300, 300)
@@ -109,7 +125,11 @@ class Load3d {
this.sceneManager.backgroundCamera
)
this.modelManager = new ModelManager(
if (options.disablePreview) {
this.previewManager.togglePreview(false)
}
this.modelManager = new SceneModelManager(
this.sceneManager.scene,
this.renderer,
this.eventManager,
@@ -142,6 +162,7 @@ class Load3d {
this.STATUS_MOUSE_ON_NODE = false
this.STATUS_MOUSE_ON_SCENE = false
this.STATUS_MOUSE_ON_VIEWER = false
this.handleResize()
this.startAnimation()
@@ -151,6 +172,41 @@ class Load3d {
}, 100)
}
getEventManager(): EventManager {
return this.eventManager
}
getNodeStorage(): NodeStorage {
return this.nodeStorage
}
getSceneManager(): SceneManager {
return this.sceneManager
}
getCameraManager(): CameraManager {
return this.cameraManager
}
getControlsManager(): ControlsManager {
return this.controlsManager
}
getLightingManager(): LightingManager {
return this.lightingManager
}
getViewHelperManager(): ViewHelperManager {
return this.viewHelperManager
}
getPreviewManager(): PreviewManager {
return this.previewManager
}
getLoaderManager(): LoaderManager {
return this.loaderManager
}
getModelManager(): SceneModelManager {
return this.modelManager
}
getRecordingManager(): RecordingManager {
return this.recordingManager
}
forceRender(): void {
const delta = this.clock.getDelta()
this.viewHelperManager.update(delta)
@@ -172,12 +228,43 @@ class Load3d {
}
renderMainScene(): void {
const width = this.renderer.domElement.clientWidth
const height = this.renderer.domElement.clientHeight
const containerWidth = this.renderer.domElement.clientWidth
const containerHeight = this.renderer.domElement.clientHeight
this.renderer.setViewport(0, 0, width, height)
this.renderer.setScissor(0, 0, width, height)
this.renderer.setScissorTest(true)
if (this.isViewerMode) {
const containerAspectRatio = containerWidth / containerHeight
let renderWidth: number
let renderHeight: number
let offsetX: number = 0
let offsetY: number = 0
if (containerAspectRatio > this.targetAspectRatio) {
renderHeight = containerHeight
renderWidth = renderHeight * this.targetAspectRatio
offsetX = (containerWidth - renderWidth) / 2
} else {
renderWidth = containerWidth
renderHeight = renderWidth / this.targetAspectRatio
offsetY = (containerHeight - renderHeight) / 2
}
this.renderer.setViewport(0, 0, containerWidth, containerHeight)
this.renderer.setScissor(0, 0, containerWidth, containerHeight)
this.renderer.setScissorTest(true)
this.renderer.setClearColor(0x0a0a0a)
this.renderer.clear()
this.renderer.setViewport(offsetX, offsetY, renderWidth, renderHeight)
this.renderer.setScissor(offsetX, offsetY, renderWidth, renderHeight)
const renderAspectRatio = renderWidth / renderHeight
this.cameraManager.updateAspectRatio(renderAspectRatio)
} else {
this.renderer.setViewport(0, 0, containerWidth, containerHeight)
this.renderer.setScissor(0, 0, containerWidth, containerHeight)
this.renderer.setScissorTest(true)
}
this.sceneManager.renderBackground()
this.renderer.render(
@@ -243,10 +330,15 @@ class Load3d {
this.STATUS_MOUSE_ON_SCENE = onScene
}
updateStatusMouseOnViewer(onViewer: boolean): void {
this.STATUS_MOUSE_ON_VIEWER = onViewer
}
isActive(): boolean {
return (
this.STATUS_MOUSE_ON_NODE ||
this.STATUS_MOUSE_ON_SCENE ||
this.STATUS_MOUSE_ON_VIEWER ||
this.isRecording() ||
!this.INITIAL_RENDER_DONE
)
@@ -308,6 +400,34 @@ class Load3d {
this.sceneManager.backgroundTexture
)
if (
this.isViewerMode &&
this.sceneManager.backgroundTexture &&
this.sceneManager.backgroundMesh
) {
const containerWidth = this.renderer.domElement.clientWidth
const containerHeight = this.renderer.domElement.clientHeight
const containerAspectRatio = containerWidth / containerHeight
let renderWidth: number
let renderHeight: number
if (containerAspectRatio > this.targetAspectRatio) {
renderHeight = containerHeight
renderWidth = renderHeight * this.targetAspectRatio
} else {
renderWidth = containerWidth
renderHeight = renderWidth / this.targetAspectRatio
}
this.sceneManager.updateBackgroundSize(
this.sceneManager.backgroundTexture,
this.sceneManager.backgroundMesh,
renderWidth,
renderHeight
)
}
this.forceRender()
}
@@ -340,6 +460,10 @@ class Load3d {
return this.cameraManager.getCurrentCameraType()
}
getCurrentModel(): THREE.Object3D | null {
return this.modelManager.currentModel
}
setCameraState(state: CameraState): void {
this.cameraManager.setCameraState(state)
@@ -397,6 +521,9 @@ class Load3d {
}
setTargetSize(width: number, height: number): void {
this.targetWidth = width
this.targetHeight = height
this.targetAspectRatio = width / height
this.previewManager.setTargetSize(width, height)
this.forceRender()
}
@@ -422,13 +549,30 @@ class Load3d {
return
}
const width = parentElement.clientWidth
const height = parentElement.clientHeight
const containerWidth = parentElement.clientWidth
const containerHeight = parentElement.clientHeight
this.cameraManager.handleResize(width, height)
this.sceneManager.handleResize(width, height)
if (this.isViewerMode) {
const containerAspectRatio = containerWidth / containerHeight
let renderWidth: number
let renderHeight: number
this.renderer.setSize(width, height)
if (containerAspectRatio > this.targetAspectRatio) {
renderHeight = containerHeight
renderWidth = renderHeight * this.targetAspectRatio
} else {
renderWidth = containerWidth
renderHeight = renderWidth / this.targetAspectRatio
}
this.cameraManager.handleResize(renderWidth, renderHeight)
this.sceneManager.handleResize(renderWidth, renderHeight)
} else {
this.cameraManager.handleResize(containerWidth, containerHeight)
this.sceneManager.handleResize(containerWidth, containerHeight)
}
this.renderer.setSize(containerWidth, containerHeight)
this.previewManager.handleResize()
this.forceRender()

View File

@@ -27,10 +27,6 @@ class Load3dAnimation extends Load3d {
this.overrideAnimationLoop()
}
private getCurrentModel(): THREE.Object3D | null {
return this.modelManager.currentModel
}
private overrideAnimationLoop(): void {
if (this.animationFrameId !== null) {
cancelAnimationFrame(this.animationFrameId)

View File

@@ -18,7 +18,7 @@ import {
UpDirection
} from './interfaces'
export class ModelManager implements ModelManagerInterface {
export class SceneModelManager implements ModelManagerInterface {
currentModel: THREE.Object3D | null = null
originalModel:
| THREE.Object3D
@@ -663,6 +663,12 @@ export class ModelManager implements ModelManagerInterface {
this.originalMaterials = new WeakMap()
}
addModelToScene(model: THREE.Object3D): void {
this.currentModel = model
this.scene.add(this.currentModel)
}
async setupModel(model: THREE.Object3D): Promise<void> {
this.currentModel = model

View File

@@ -37,6 +37,8 @@ export interface EventCallback {
export interface Load3DOptions {
node?: LGraphNode
inputSpec?: CustomInputSpec
disablePreview?: boolean
isViewerMode?: boolean
}
export interface CaptureResult {
@@ -159,6 +161,7 @@ export interface ModelManagerInterface {
clearModel(): void
reset(): void
setupModel(model: THREE.Object3D): Promise<void>
addModelToScene(model: THREE.Object3D): void
setOriginalModel(model: THREE.Object3D | THREE.BufferGeometry | GLTF): void
setUpDirection(direction: UpDirection): void
materialMode: MaterialMode