import * as THREE from "three" ; import { OBJLoader } from "three/examples/jsm/loaders/OBJLoader" ; import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader" ; import { OrbitControls } from "three-orbitcontrols-ts" ; import { DesignerNode } from "./designer/designernode" ; import { ImageCanvas } from "./designer/imagecanvas" ; import { SphereGeometry } from "./geometry/sphere" ; import { CylinderGeometry } from "./geometry/cylinder" ; import { PlaneGeometry } from "./geometry/plane" ; import path from "path" ; import { DataTexture } from "three" ; export class View3D { private camera ! : THREE . PerspectiveCamera ; private renderer ! : THREE . WebGLRenderer ; private scene : THREE . Scene = new THREE . Scene ( ) ; private material : THREE . MeshStandardMaterial = new THREE . MeshStandardMaterial ( { color : 0xffffff , roughness : 0.5 , metalness : 0.0 , transparent : true , alphaTest : 0 , side : THREE . DoubleSide } ) ; private cubeMap : THREE . CubeTexture ; private skyPath : string = "assets/env/wide_street_01_1k.hdr" ; private model : THREE . Object3D ; private controls : OrbitControls ; private repeat = 1 ; private sphereGeom = new SphereGeometry ( 0.7 , 128 , 128 ) ; private cubeGeom = new THREE . BoxGeometry ( ) ; private planeGeom = new PlaneGeometry ( 2 , 2 , 100 , 100 ) ; private cylinderGeom = new CylinderGeometry ( 0.5 , 0.5 , 1 , 64 , 64 , true ) ; setSkyPath ( path ) { this . skyPath = path ; this . loadEnvEquirect ( ) ; } setCanvas ( el ) { this . setupRenderer ( el ) ; this . camera = new THREE . PerspectiveCamera ( 45 , el . width / el . height , 0.1 , 1000 ) ; this . camera . position . z = 2.6 ; this . camera . position . y = 1 ; this . camera . position . x = 1 ; this . setupOrbitControls ( el ) ; this . setupLighting ( ) ; this . loadEnvEquirect ( ) ; this . model = new THREE . Mesh ( this . sphereGeom , this . material ) ; this . scene . add ( this . model ) ; const animate = ( ) => { requestAnimationFrame ( animate ) ; this . controls . update ( ) ; this . renderer . render ( this . scene , this . camera ) ; } ; animate ( ) ; } setupRenderer ( el ) { const renderer = new THREE . WebGLRenderer ( { alpha : true , canvas : el , preserveDrawingBuffer : true , antialias : true } ) ; renderer . setClearColor ( 0x000000 , 0 ) ; renderer . physicallyCorrectLights = true ; renderer . gammaFactor = 2.2 ; renderer . outputEncoding = THREE . GammaEncoding ; renderer . shadowMap . enabled = true ; renderer . shadowMap . type = THREE . PCFSoftShadowMap ; renderer . setSize ( el . width , el . height ) ; renderer . toneMapping = THREE . ACESFilmicToneMapping ; this . renderer = renderer ; } setupLighting ( ) { const container = new THREE . Object3D ( ) ; const brightness = 2 ; let object3d = new THREE . DirectionalLight ( "white" , 0.225 * brightness ) ; object3d . position . set ( 2.6 , 1 , 3 ) ; object3d . name = "Back light" ; container . add ( object3d ) ; object3d = new THREE . DirectionalLight ( "white" , 0.375 * brightness ) ; object3d . position . set ( - 2 , - 1 , 0 ) ; object3d . name = "Key light" ; container . add ( object3d ) ; object3d = new THREE . DirectionalLight ( "white" , 0.75 * brightness ) ; object3d . position . set ( 3 , 3 , 2 ) ; object3d . name = "Fill light" ; container . add ( object3d ) ; } setupOrbitControls ( el ) { const controls = new OrbitControls ( this . camera , el ) ; controls . enableZoom = true ; controls . enableRotate = true ; controls . enablePan = true ; controls . keyPanSpeed = 7.0 ; controls . enableKeys = true ; controls . target = new THREE . Vector3 ( 0 , 0 , 0 ) ; controls . mouseButtons . PAN = null ; controls . keys = { LEFT : 0 , UP : 0 , RIGHT : 0 , BOTTOM : 0 } ; this . controls = controls ; } loadEnvironment ( basePath ) { } resize ( width , height ) { this . camera . aspect = width / height ; this . camera . updateProjectionMatrix ( ) ; this . renderer . setSize ( width , height ) ; } private _init ( ) { } setTexture ( imageCanvas , channelName ) { if ( channelName == "albedo" ) this . setAlbedoTexture ( imageCanvas , channelName ) ; if ( channelName == "normal" ) this . setNormalTexture ( imageCanvas , channelName ) ; if ( channelName == "metalness" ) this . setMetalnessTexture ( imageCanvas , channelName ) ; if ( channelName == "roughness" ) this . setRoughnessTexture ( imageCanvas , channelName ) ; if ( channelName == "height" ) this . setHeightTexture ( imageCanvas , channelName ) ; if ( channelName == "emission" ) this . setEmissionTexture ( imageCanvas , channelName ) ; if ( channelName == "ao" ) this . setAoTexture ( imageCanvas , channelName ) ; if ( channelName == "alpha" ) this . setAlphaTexture ( imageCanvas , channelName ) ; } clearTexture ( channelName ) { if ( channelName == "albedo" ) { this . material . map = null ; } if ( channelName == "normal" ) { this . material . normalMap = null ; } if ( channelName == "metalness" ) { this . material . metalnessMap = null ; this . material . metalness = 0 ; } if ( channelName == "roughness" ) { this . material . roughnessMap = null ; this . material . roughness = 0.5 ; } if ( channelName == "height" ) { this . material . displacementMap = null ; } if ( channelName == "emission" ) { this . material . emissiveMap = null ; } if ( channelName == "ao" ) { this . material . aoMap = null ; } if ( channelName == "alpha" ) { this . material . alphaMap = null ; } this . material . needsUpdate = true ; } updateTexture ( channelName ) { if ( channelName == "albedo" && this . material . map != null ) { this . material . map . needsUpdate = true ; } if ( channelName == "normal" && this . material . normalMap != null ) { this . material . normalMap . needsUpdate = true ; } if ( channelName == "metalness" && this . material . metalnessMap != null ) { this . material . metalnessMap . needsUpdate = true ; } if ( channelName == "roughness" && this . material . roughnessMap != null ) { this . material . roughnessMap . needsUpdate = true ; } if ( channelName == "height" && this . material . displacementMap != null ) { this . material . displacementMap . needsUpdate = true ; } if ( channelName == "emission" && this . material . emissiveMap != null ) { this . material . emissiveMap . needsUpdate = true ; } if ( channelName == "ao" && this . material . aoMap != null ) { this . material . aoMap . needsUpdate = true ; } if ( channelName == "alpha" && this . material . alphaMap != null ) { this . material . alphaMap . needsUpdate = true ; } this . material . needsUpdate = true ; } setAlbedoTexture ( imageCanvas , channelName ) { const tex = new THREE . CanvasTexture ( imageCanvas . canvas ) ; tex . wrapS = tex . wrapT = THREE . RepeatWrapping ; tex . repeat . set ( this . repeat , this . repeat ) ; tex . anisotropy = this . renderer . capabilities . getMaxAnisotropy ( ) ; tex . encoding = THREE . GammaEncoding ; tex . needsUpdate = true ; this . material . map = tex ; this . material . needsUpdate = true ; } setNormalTexture ( imageCanvas , channelName ) { const tex = new THREE . CanvasTexture ( imageCanvas . canvas ) ; tex . wrapS = tex . wrapT = THREE . RepeatWrapping ; tex . repeat . set ( this . repeat , this . repeat ) ; tex . anisotropy = this . renderer . capabilities . getMaxAnisotropy ( ) ; tex . needsUpdate = true ; this . material . normalMap = tex ; this . material . needsUpdate = true ; } setMetalnessTexture ( imageCanvas , channelName ) { const tex = new THREE . CanvasTexture ( imageCanvas . canvas ) ; tex . wrapS = tex . wrapT = THREE . RepeatWrapping ; tex . repeat . set ( this . repeat , this . repeat ) ; tex . anisotropy = this . renderer . capabilities . getMaxAnisotropy ( ) ; tex . needsUpdate = true ; this . material . metalnessMap = tex ; this . material . metalness = 1.0 ; this . material . needsUpdate = true ; } setRoughnessTexture ( imageCanvas , channelName ) { const tex = new THREE . CanvasTexture ( imageCanvas . canvas ) ; tex . wrapS = tex . wrapT = THREE . RepeatWrapping ; tex . repeat . set ( this . repeat , this . repeat ) ; tex . anisotropy = this . renderer . capabilities . getMaxAnisotropy ( ) ; tex . needsUpdate = true ; this . material . roughnessMap = tex ; this . material . roughness = 1.0 ; this . material . needsUpdate = true ; } setHeightTexture ( imageCanvas , channelName ) { const tex = new THREE . CanvasTexture ( imageCanvas . canvas ) ; tex . wrapS = tex . wrapT = THREE . RepeatWrapping ; tex . repeat . set ( this . repeat , this . repeat ) ; tex . anisotropy = this . renderer . capabilities . getMaxAnisotropy ( ) ; tex . needsUpdate = true ; this . material . displacementMap = tex ; this . material . displacementScale = 0.1 ; this . material . needsUpdate = true ; } setEmissionTexture ( imageCanvas , channelName ) { console . log ( "setting emission texture" ) ; const tex = new THREE . CanvasTexture ( imageCanvas . canvas ) ; tex . wrapS = tex . wrapT = THREE . RepeatWrapping ; tex . repeat . set ( this . repeat , this . repeat ) ; tex . anisotropy = this . renderer . capabilities . getMaxAnisotropy ( ) ; tex . encoding = THREE . GammaEncoding ; tex . needsUpdate = true ; this . material . emissiveMap = tex ; this . material . emissive = new THREE . Color ( 1 , 1 , 1 ) ; this . material . needsUpdate = true ; } setAoTexture ( imageCanvas , channelName ) { const tex = new THREE . CanvasTexture ( imageCanvas . canvas ) ; tex . wrapS = tex . wrapT = THREE . RepeatWrapping ; tex . repeat . set ( this . repeat , this . repeat ) ; tex . anisotropy = this . renderer . capabilities . getMaxAnisotropy ( ) ; tex . needsUpdate = true ; this . material . aoMap = tex ; this . material . needsUpdate = true ; } setAlphaTexture ( imageCanvas , channelName ) { const tex = new THREE . CanvasTexture ( imageCanvas . canvas ) ; tex . wrapS = tex . wrapT = THREE . RepeatWrapping ; tex . repeat . set ( this . repeat , this . repeat ) ; tex . anisotropy = this . renderer . capabilities . getMaxAnisotropy ( ) ; tex . needsUpdate = true ; this . material . alphaMap = tex ; this . material . needsUpdate = true ; } setRepeat ( repeat ) { this . repeat = repeat ; const mat = this . material ; if ( mat . map ) { mat . map . repeat . set ( repeat , repeat ) ; mat . map . needsUpdate = true ; } if ( mat . normalMap ) { mat . normalMap . repeat . set ( repeat , repeat ) ; mat . normalMap . needsUpdate = true ; } if ( mat . metalnessMap ) { mat . metalnessMap . repeat . set ( repeat , repeat ) ; mat . metalnessMap . needsUpdate = true ; } if ( mat . roughnessMap ) { mat . roughnessMap . repeat . set ( repeat , repeat ) ; mat . roughnessMap . needsUpdate = true ; } if ( mat . displacementMap ) { mat . displacementMap . repeat . set ( repeat , repeat ) ; mat . displacementMap . needsUpdate = true ; } if ( mat . alphaMap ) { mat . alphaMap . repeat . set ( repeat , repeat ) ; mat . alphaMap . needsUpdate = true ; } if ( mat . emissiveMap ) { mat . emissiveMap . repeat . set ( repeat , repeat ) ; mat . emissiveMap . needsUpdate = true ; } if ( mat . aoMap ) { mat . aoMap . repeat . set ( repeat , repeat ) ; mat . aoMap . needsUpdate = true ; } } setModel ( modelName ) { if ( this . model ) this . scene . remove ( this . model ) ; let geom ; if ( modelName == "sphere" ) geom = this . sphereGeom ; if ( modelName == "cube" ) geom = this . cubeGeom ; if ( modelName == "plane" ) geom = this . planeGeom ; if ( modelName == "cylinder" ) geom = this . cylinderGeom ; this . model = new THREE . Mesh ( geom , this . material ) ; this . scene . add ( this . model ) ; } loadModel ( modelPath ) { if ( this . model ) this . scene . remove ( this . model ) ; let loader = new OBJLoader ( ) ; loader . load ( modelPath , group => { for ( let child of group . children ) if ( child instanceof THREE . Mesh ) ( child as THREE . Mesh ) . material = this . material ; this . model = group ; this . scene . add ( this . model ) ; } ) ; } reset ( ) { this . material = new THREE . MeshStandardMaterial ( { color : 0xffffff , roughness : 0.5 , metalness : 0.0 , side : THREE . DoubleSide } ) ; this . loadEnvEquirect ( ) ; ( this . model as THREE . Mesh ) . material = this . material ; } setToneMappingExposure ( exposure ) { this . renderer . toneMappingExposure = exposure ; } loadEnv ( ) { const basePath = ( process . env . NODE_ENV == "production" ? "file://" : "" ) + path . join ( process . env . BASE_URL , "assets/env/SwedishRoyalCastle/" ) ; const format = ".jpg" ; const envMap = new THREE . CubeTextureLoader ( ) . load ( [ basePath + "posx" + format , basePath + "negx" + format , basePath + "posy" + format , basePath + "negy" + format , basePath + "posz" + format , basePath + "negz" + format ] ) ; return envMap ; } loadEnvEquirect ( ) { const skyPath = this . skyPath ; const hdrPath = ( process . env . NODE_ENV == "production" ? "file://" : "" ) + path . join ( process . env . BASE_URL , skyPath ) ; new RGBELoader ( ) . setDataType ( THREE . UnsignedByteType ) . load ( hdrPath , ( texture ) => { if ( skyPath !== this . skyPath ) return ; const pmremGenerator = new THREE . PMREMGenerator ( this . renderer ) ; pmremGenerator . compileEquirectangularShader ( ) ; const evtRT = pmremGenerator . fromEquirectangular ( texture ) ; const tex = evtRT . texture ; tex . minFilter = THREE . LinearFilter ; tex . magFilter = THREE . LinearFilter ; this . material . envMap = evtRT . texture ; this . material . needsUpdate = true ; this . scene . background = evtRT . texture ; } ) ; } }