Spaces:
Running
Running
Update viewer.js
Browse files
viewer.js
CHANGED
|
@@ -23,12 +23,19 @@ async function loadImageAsTexture(url, app) {
|
|
| 23 |
let pc;
|
| 24 |
export let app = null;
|
| 25 |
let cameraEntity = null;
|
|
|
|
| 26 |
let modelEntity = null;
|
| 27 |
let tubeEntity = null;
|
| 28 |
let filtreEntity = null;
|
|
|
|
|
|
|
| 29 |
let viewerInitialized = false;
|
| 30 |
let resizeObserver = null;
|
| 31 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
// Materials (for real-time switching)
|
| 33 |
let matTransparent = null;
|
| 34 |
let matOpaque = null;
|
|
@@ -134,7 +141,7 @@ export async function initializeViewer(config, instanceId) {
|
|
| 134 |
app.setCanvasFillMode(pc.FILLMODE_NONE);
|
| 135 |
app.setCanvasResolution(pc.RESOLUTION_AUTO);
|
| 136 |
|
| 137 |
-
// Canvas responsive resize
|
| 138 |
resizeObserver = new ResizeObserver(entries => {
|
| 139 |
entries.forEach(entry => {
|
| 140 |
app.resizeCanvas(entry.contentRect.width, entry.contentRect.height);
|
|
@@ -145,12 +152,11 @@ export async function initializeViewer(config, instanceId) {
|
|
| 145 |
app.on('destroy', () => resizeObserver.disconnect());
|
| 146 |
|
| 147 |
// ----- Load all images as Textures (async, safe for CORS) -----
|
| 148 |
-
//
|
| 149 |
const hdrTex = await loadImageAsTexture('https://huggingface.co/datasets/MikaFil/3D_models/resolve/main/EARCARE/hdr/ciel_nuageux_1k.png', app);
|
| 150 |
const emitTex = await loadImageAsTexture('https://huggingface.co/datasets/MikaFil/3D_models/resolve/main/EARCARE/textures/emit_map_1k.png', app);
|
| 151 |
const opTex = await loadImageAsTexture('https://huggingface.co/datasets/MikaFil/3D_models/resolve/main/EARCARE/textures/op_map_1k.png', app);
|
| 152 |
const thicknessTex= await loadImageAsTexture('https://huggingface.co/datasets/MikaFil/3D_models/resolve/main/EARCARE/textures/thickness_map_1k.png', app);
|
| 153 |
-
const bgTex = await loadImageAsTexture('https://huggingface.co/datasets/MikaFil/3D_models/resolve/main/EARCARE/images/banniere_earcare.png', app);
|
| 154 |
|
| 155 |
// ----- GLB asset definition -----
|
| 156 |
const assets = {
|
|
@@ -167,102 +173,134 @@ export async function initializeViewer(config, instanceId) {
|
|
| 167 |
app.scene.skyboxIntensity = 4;
|
| 168 |
app.scene.skyboxMip = 0;
|
| 169 |
|
| 170 |
-
// -----
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
const
|
| 177 |
-
width:
|
| 178 |
-
height:
|
| 179 |
format: pc.PIXELFORMAT_SRGBA8,
|
| 180 |
-
mipmaps:
|
| 181 |
-
addressU: pc.ADDRESS_CLAMP_TO_EDGE,
|
| 182 |
-
addressV: pc.ADDRESS_CLAMP_TO_EDGE
|
| 183 |
});
|
| 184 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 185 |
name: 'RT',
|
| 186 |
-
colorBuffer:
|
| 187 |
depth: true,
|
| 188 |
-
|
| 189 |
-
samples: 2
|
| 190 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 191 |
|
| 192 |
-
|
|
|
|
| 193 |
app.scene.layers.insert(excludedLayer, 1);
|
| 194 |
|
| 195 |
-
|
| 196 |
-
const worldLayer
|
| 197 |
const skyboxLayer = app.scene.layers.getLayerByName('Skybox');
|
| 198 |
-
const uiLayer
|
| 199 |
|
| 200 |
-
// Reorder depth layer for transmission
|
| 201 |
const depthLayer = app.scene.layers.getLayerById(pc.LAYERID_DEPTH);
|
| 202 |
app.scene.layers.remove(depthLayer);
|
| 203 |
app.scene.layers.insertOpaque(depthLayer, 2);
|
| 204 |
|
| 205 |
-
// Instantiate GLB entities
|
| 206 |
-
modelEntity
|
| 207 |
-
tubeEntity
|
| 208 |
filtreEntity = assets.filtre.resource.instantiateRenderEntity();
|
| 209 |
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
traverse(filtreEntity, (node) => {
|
| 214 |
-
if (node.render) node.render.layers = [excludedLayer.id];
|
| 215 |
-
});
|
| 216 |
|
| 217 |
app.root.addChild(modelEntity);
|
| 218 |
app.root.addChild(tubeEntity);
|
| 219 |
app.root.addChild(filtreEntity);
|
| 220 |
|
| 221 |
-
|
| 222 |
// ----- Materials Setup -----
|
| 223 |
// Transparent material (main model)
|
| 224 |
-
{
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
|
|
|
| 244 |
}
|
| 245 |
// Opaque material (main model)
|
| 246 |
-
{
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
|
|
|
| 261 |
// Transparent material (tube)
|
| 262 |
-
{
|
|
|
|
| 263 |
tubeTransparent.diffuse = new pc.Color(1, 1, 1);
|
| 264 |
tubeTransparent.blendType = pc.BLEND_NORMAL;
|
| 265 |
-
tubeTransparent.opacity = 0.95;
|
| 266 |
tubeTransparent.depthTest = false;
|
| 267 |
tubeTransparent.depthWrite = false;
|
| 268 |
tubeTransparent.useMetalness = true;
|
|
@@ -271,11 +309,13 @@ export async function initializeViewer(config, instanceId) {
|
|
| 271 |
tubeTransparent.update();
|
| 272 |
}
|
| 273 |
// Opaque material (tube)
|
| 274 |
-
{
|
|
|
|
| 275 |
tubeOpaque.diffuse = new pc.Color(1, 1, 1);
|
| 276 |
-
tubeOpaque.opacity = 0.9;
|
| 277 |
tubeOpaque.update();
|
| 278 |
}
|
|
|
|
| 279 |
// ----- Assign materials to meshes -----
|
| 280 |
traverse(modelEntity, node => {
|
| 281 |
if (node.render && node.render.meshInstances) {
|
|
@@ -305,7 +345,7 @@ export async function initializeViewer(config, instanceId) {
|
|
| 305 |
filtreEntity.setLocalScale(modelScale, modelScale, modelScale);
|
| 306 |
filtreEntity.setLocalEulerAngles(modelRotationX, modelRotationY, modelRotationZ);
|
| 307 |
|
| 308 |
-
// -----
|
| 309 |
cameraEntity = new pc.Entity('camera');
|
| 310 |
cameraEntity.addComponent('camera', {
|
| 311 |
clearColor: new pc.Color(0.8, 0.8, 0.8, 1),
|
|
@@ -335,60 +375,39 @@ export async function initializeViewer(config, instanceId) {
|
|
| 335 |
cameraEntity.script.create('orbitCameraInputTouch');
|
| 336 |
app.root.addChild(cameraEntity);
|
| 337 |
|
| 338 |
-
//
|
| 339 |
-
|
| 340 |
-
/*if (skyboxLayer) {
|
| 341 |
-
const camLayers = cameraEntity.camera.layers.slice();
|
| 342 |
-
const idx = camLayers.indexOf(skyboxLayer.id);
|
| 343 |
-
if (idx !== -1) {
|
| 344 |
-
camLayers.splice(idx, 1);
|
| 345 |
-
cameraEntity.camera.layers = camLayers;
|
| 346 |
-
}
|
| 347 |
-
}*/
|
| 348 |
|
| 349 |
-
|
| 350 |
textureCamera.addComponent('camera', {
|
| 351 |
-
clearColor: new pc.Color(0
|
| 352 |
-
layers: [excludedLayer.id
|
| 353 |
toneMapping: pc.TONEMAP_NEUTRAL,
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
// to make it rendered first each frame
|
| 357 |
-
priority: -1,
|
| 358 |
-
|
| 359 |
-
// this camera renders into texture target
|
| 360 |
-
renderTarget: renderTarget
|
| 361 |
});
|
|
|
|
| 362 |
cameraEntity.addChild(textureCamera);
|
|
|
|
|
|
|
| 363 |
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
if (idx !== -1) {
|
| 368 |
-
camLayers.splice(idx, 1);
|
| 369 |
-
textureCamera.camera.layers = camLayers;
|
| 370 |
-
}
|
| 371 |
-
}*/
|
| 372 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 373 |
|
| 374 |
-
|
| 375 |
-
|
| 376 |
-
|
| 377 |
-
bgPlane.setLocalPosition(0, 0, -10);
|
| 378 |
-
bgPlane.setLocalScale(11, 1, 5.5);
|
| 379 |
-
bgPlane.setLocalEulerAngles(90, 0, 0);
|
| 380 |
-
// Simple material for the banner
|
| 381 |
-
const mat = new pc.StandardMaterial();
|
| 382 |
-
mat.emissive = new pc.Color(1, 1, 1);
|
| 383 |
-
mat.emissiveMap = texture;
|
| 384 |
-
mat.emissiveIntensity = 1;
|
| 385 |
-
mat.useLighting = false;
|
| 386 |
-
mat.cull = pc.CULLFACE_NONE;
|
| 387 |
-
mat.update();
|
| 388 |
-
bgPlane.model.material = mat;
|
| 389 |
-
cameraEntity.addChild(bgPlane);
|
| 390 |
-
|
| 391 |
-
|
| 392 |
|
| 393 |
// ----- Lighting -----
|
| 394 |
const light = new pc.Entity("mainLight");
|
|
@@ -401,6 +420,23 @@ export async function initializeViewer(config, instanceId) {
|
|
| 401 |
light.lookAt(0, 0, 0);
|
| 402 |
app.root.addChild(light);
|
| 403 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 404 |
app.resizeCanvas(viewerContainer.clientWidth, viewerContainer.clientHeight);
|
| 405 |
app.once('update', () => resetViewerCamera());
|
| 406 |
|
|
|
|
| 23 |
let pc;
|
| 24 |
export let app = null;
|
| 25 |
let cameraEntity = null;
|
| 26 |
+
let textureCamera = null;
|
| 27 |
let modelEntity = null;
|
| 28 |
let tubeEntity = null;
|
| 29 |
let filtreEntity = null;
|
| 30 |
+
let bgPlane = null;
|
| 31 |
+
let bgMat = null;
|
| 32 |
let viewerInitialized = false;
|
| 33 |
let resizeObserver = null;
|
| 34 |
|
| 35 |
+
// Render target globals (pour resize)
|
| 36 |
+
let rtTexture = null;
|
| 37 |
+
let renderTarget = null;
|
| 38 |
+
|
| 39 |
// Materials (for real-time switching)
|
| 40 |
let matTransparent = null;
|
| 41 |
let matOpaque = null;
|
|
|
|
| 141 |
app.setCanvasFillMode(pc.FILLMODE_NONE);
|
| 142 |
app.setCanvasResolution(pc.RESOLUTION_AUTO);
|
| 143 |
|
| 144 |
+
// Canvas responsive resize (déclenchera app.on('resize'))
|
| 145 |
resizeObserver = new ResizeObserver(entries => {
|
| 146 |
entries.forEach(entry => {
|
| 147 |
app.resizeCanvas(entry.contentRect.width, entry.contentRect.height);
|
|
|
|
| 152 |
app.on('destroy', () => resizeObserver.disconnect());
|
| 153 |
|
| 154 |
// ----- Load all images as Textures (async, safe for CORS) -----
|
| 155 |
+
// Note : envAtlas ci-dessous utilise une simple image comme placeholder.
|
| 156 |
const hdrTex = await loadImageAsTexture('https://huggingface.co/datasets/MikaFil/3D_models/resolve/main/EARCARE/hdr/ciel_nuageux_1k.png', app);
|
| 157 |
const emitTex = await loadImageAsTexture('https://huggingface.co/datasets/MikaFil/3D_models/resolve/main/EARCARE/textures/emit_map_1k.png', app);
|
| 158 |
const opTex = await loadImageAsTexture('https://huggingface.co/datasets/MikaFil/3D_models/resolve/main/EARCARE/textures/op_map_1k.png', app);
|
| 159 |
const thicknessTex= await loadImageAsTexture('https://huggingface.co/datasets/MikaFil/3D_models/resolve/main/EARCARE/textures/thickness_map_1k.png', app);
|
|
|
|
| 160 |
|
| 161 |
// ----- GLB asset definition -----
|
| 162 |
const assets = {
|
|
|
|
| 173 |
app.scene.skyboxIntensity = 4;
|
| 174 |
app.scene.skyboxMip = 0;
|
| 175 |
|
| 176 |
+
// ----- RT helpers -----
|
| 177 |
+
function createRT(w, h) {
|
| 178 |
+
if (rtTexture) {
|
| 179 |
+
rtTexture.destroy();
|
| 180 |
+
rtTexture = null;
|
| 181 |
+
}
|
| 182 |
+
const tex = new pc.Texture(app.graphicsDevice, {
|
| 183 |
+
width: w,
|
| 184 |
+
height: h,
|
| 185 |
format: pc.PIXELFORMAT_SRGBA8,
|
| 186 |
+
mipmaps: false // pas de mips pour une RT dynamique plein écran
|
|
|
|
|
|
|
| 187 |
});
|
| 188 |
+
tex.minFilter = pc.FILTER_LINEAR;
|
| 189 |
+
tex.magFilter = pc.FILTER_LINEAR;
|
| 190 |
+
tex.addressU = pc.ADDRESS_CLAMP_TO_EDGE;
|
| 191 |
+
tex.addressV = pc.ADDRESS_CLAMP_TO_EDGE;
|
| 192 |
+
|
| 193 |
+
const rt = new pc.RenderTarget({
|
| 194 |
name: 'RT',
|
| 195 |
+
colorBuffer: tex,
|
| 196 |
depth: true,
|
| 197 |
+
samples: 1 // évite un resolve MSAA pour la réutiliser comme texture
|
|
|
|
| 198 |
});
|
| 199 |
+
return { tex, rt };
|
| 200 |
+
}
|
| 201 |
+
|
| 202 |
+
function copyCam(src, dst) {
|
| 203 |
+
dst.camera.projection = src.camera.projection; // (perspective par défaut)
|
| 204 |
+
dst.camera.fov = src.camera.fov;
|
| 205 |
+
dst.camera.nearClip = src.camera.nearClip;
|
| 206 |
+
dst.camera.farClip = src.camera.farClip;
|
| 207 |
+
dst.camera.rect.set(src.camera.rect.x, src.camera.rect.y, src.camera.rect.z, src.camera.rect.w);
|
| 208 |
+
}
|
| 209 |
+
|
| 210 |
+
function fitFullscreenPlane(camEntity, planeEntity, d = 0.5) {
|
| 211 |
+
const cam = camEntity.camera;
|
| 212 |
+
const aspect = app.graphicsDevice.width / app.graphicsDevice.height;
|
| 213 |
+
const fovRad = cam.fov * pc.math.DEG_TO_RAD;
|
| 214 |
+
const h = 2 * d * Math.tan(fovRad / 2);
|
| 215 |
+
const w = h * aspect;
|
| 216 |
+
|
| 217 |
+
// en espace caméra, face caméra
|
| 218 |
+
planeEntity.reparent(camEntity);
|
| 219 |
+
planeEntity.setLocalPosition(0, 0, -d);
|
| 220 |
+
planeEntity.setLocalEulerAngles(-90, 0, 0); // plane (normale +Y) -> face -Z
|
| 221 |
+
planeEntity.setLocalScale(w, h, 1);
|
| 222 |
+
}
|
| 223 |
+
|
| 224 |
+
// ----- Load GLBs -----
|
| 225 |
+
const loader = new pc.AssetListLoader(Object.values(assets), app.assets);
|
| 226 |
+
loader.load(() => {
|
| 227 |
+
app.start();
|
| 228 |
+
if (progressDialog) progressDialog.style.display = 'none';
|
| 229 |
|
| 230 |
+
// --- Layers ---
|
| 231 |
+
const excludedLayer = new pc.Layer({ name: 'Excluded' });
|
| 232 |
app.scene.layers.insert(excludedLayer, 1);
|
| 233 |
|
| 234 |
+
// get existing layers
|
| 235 |
+
const worldLayer = app.scene.layers.getLayerByName('World');
|
| 236 |
const skyboxLayer = app.scene.layers.getLayerByName('Skybox');
|
| 237 |
+
const uiLayer = app.scene.layers.getLayerByName('UI');
|
| 238 |
|
| 239 |
+
// Reorder depth layer for transmission (si utile)
|
| 240 |
const depthLayer = app.scene.layers.getLayerById(pc.LAYERID_DEPTH);
|
| 241 |
app.scene.layers.remove(depthLayer);
|
| 242 |
app.scene.layers.insertOpaque(depthLayer, 2);
|
| 243 |
|
| 244 |
+
// --- Instantiate GLB entities ---
|
| 245 |
+
modelEntity = assets.model.resource.instantiateRenderEntity();
|
| 246 |
+
tubeEntity = assets.tube.resource.instantiateRenderEntity();
|
| 247 |
filtreEntity = assets.filtre.resource.instantiateRenderEntity();
|
| 248 |
|
| 249 |
+
// mettre tube/filtre dans la couche Excluded
|
| 250 |
+
traverse(tubeEntity, (node) => { if (node.render) node.render.layers = [excludedLayer.id]; });
|
| 251 |
+
traverse(filtreEntity, (node) => { if (node.render) node.render.layers = [excludedLayer.id]; });
|
|
|
|
|
|
|
|
|
|
| 252 |
|
| 253 |
app.root.addChild(modelEntity);
|
| 254 |
app.root.addChild(tubeEntity);
|
| 255 |
app.root.addChild(filtreEntity);
|
| 256 |
|
|
|
|
| 257 |
// ----- Materials Setup -----
|
| 258 |
// Transparent material (main model)
|
| 259 |
+
{
|
| 260 |
+
matTransparent = new pc.StandardMaterial();
|
| 261 |
+
matTransparent.blendType = pc.BLEND_NORMAL;
|
| 262 |
+
matTransparent.diffuse = new pc.Color(1, 1, 1);
|
| 263 |
+
matTransparent.specular = new pc.Color(0.01, 0.01, 0.01);
|
| 264 |
+
matTransparent.gloss = 1;
|
| 265 |
+
matTransparent.metalness = 0;
|
| 266 |
+
matTransparent.useMetalness = true;
|
| 267 |
+
matTransparent.useDynamicRefraction = true;
|
| 268 |
+
matTransparent.depthWrite = true;
|
| 269 |
+
matTransparent.refraction = 0.8;
|
| 270 |
+
matTransparent.refractionIndex = 1;
|
| 271 |
+
matTransparent.thickness = 0.02;
|
| 272 |
+
matTransparent.thicknessMap = thicknessTex;
|
| 273 |
+
matTransparent.opacityMap = opTex;
|
| 274 |
+
matTransparent.opacityMapChannel = "r";
|
| 275 |
+
matTransparent.opacity = 0.97;
|
| 276 |
+
matTransparent.emissive = new pc.Color(1, 1, 1);
|
| 277 |
+
matTransparent.emissiveMap = emitTex;
|
| 278 |
+
matTransparent.emissiveIntensity = 0.1;
|
| 279 |
+
matTransparent.update();
|
| 280 |
}
|
| 281 |
// Opaque material (main model)
|
| 282 |
+
{
|
| 283 |
+
matOpaque = new pc.StandardMaterial();
|
| 284 |
+
matOpaque.blendType = pc.BLEND_NORMAL;
|
| 285 |
+
matOpaque.diffuse = new pc.Color(0.7, 0.05, 0.05);
|
| 286 |
+
matOpaque.specular = new pc.Color(0.01, 0.01, 0.01);
|
| 287 |
+
matOpaque.specularityFactor = 1;
|
| 288 |
+
matOpaque.gloss = 1;
|
| 289 |
+
matOpaque.metalness = 0;
|
| 290 |
+
matOpaque.opacityMap = opTex;
|
| 291 |
+
matOpaque.opacityMapChannel = "r";
|
| 292 |
+
matOpaque.opacity = 1;
|
| 293 |
+
matOpaque.emissive = new pc.Color(0.372, 0.03, 0.003);
|
| 294 |
+
matOpaque.emissiveMap = emitTex;
|
| 295 |
+
matOpaque.emissiveIntensity = 2;
|
| 296 |
+
matOpaque.update();
|
| 297 |
+
}
|
| 298 |
// Transparent material (tube)
|
| 299 |
+
{
|
| 300 |
+
tubeTransparent = new pc.StandardMaterial();
|
| 301 |
tubeTransparent.diffuse = new pc.Color(1, 1, 1);
|
| 302 |
tubeTransparent.blendType = pc.BLEND_NORMAL;
|
| 303 |
+
tubeTransparent.opacity = 0.95;
|
| 304 |
tubeTransparent.depthTest = false;
|
| 305 |
tubeTransparent.depthWrite = false;
|
| 306 |
tubeTransparent.useMetalness = true;
|
|
|
|
| 309 |
tubeTransparent.update();
|
| 310 |
}
|
| 311 |
// Opaque material (tube)
|
| 312 |
+
{
|
| 313 |
+
tubeOpaque = new pc.StandardMaterial();
|
| 314 |
tubeOpaque.diffuse = new pc.Color(1, 1, 1);
|
| 315 |
+
tubeOpaque.opacity = 0.9;
|
| 316 |
tubeOpaque.update();
|
| 317 |
}
|
| 318 |
+
|
| 319 |
// ----- Assign materials to meshes -----
|
| 320 |
traverse(modelEntity, node => {
|
| 321 |
if (node.render && node.render.meshInstances) {
|
|
|
|
| 345 |
filtreEntity.setLocalScale(modelScale, modelScale, modelScale);
|
| 346 |
filtreEntity.setLocalEulerAngles(modelRotationX, modelRotationY, modelRotationZ);
|
| 347 |
|
| 348 |
+
// ----- Main camera + Orbit Controls -----
|
| 349 |
cameraEntity = new pc.Entity('camera');
|
| 350 |
cameraEntity.addComponent('camera', {
|
| 351 |
clearColor: new pc.Color(0.8, 0.8, 0.8, 1),
|
|
|
|
| 375 |
cameraEntity.script.create('orbitCameraInputTouch');
|
| 376 |
app.root.addChild(cameraEntity);
|
| 377 |
|
| 378 |
+
// ----- Texture camera (rend Excluded vers RT) -----
|
| 379 |
+
({ tex: rtTexture, rt: renderTarget } = createRT(app.graphicsDevice.width, app.graphicsDevice.height));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 380 |
|
| 381 |
+
textureCamera = new pc.Entity('TextureCamera');
|
| 382 |
textureCamera.addComponent('camera', {
|
| 383 |
+
clearColor: new pc.Color(0, 0, 0, 0),
|
| 384 |
+
layers: [excludedLayer.id /*, skyboxLayer.id*/],
|
| 385 |
toneMapping: pc.TONEMAP_NEUTRAL,
|
| 386 |
+
priority: -1, // rendre avant la caméra principale
|
| 387 |
+
renderTarget: renderTarget // sortie vers la RT
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 388 |
});
|
| 389 |
+
// on veut la même pose → enfant de la caméra principale
|
| 390 |
cameraEntity.addChild(textureCamera);
|
| 391 |
+
// et la même projection
|
| 392 |
+
copyCam(cameraEntity, textureCamera);
|
| 393 |
|
| 394 |
+
// ----- Background plane plein écran (espace caméra) -----
|
| 395 |
+
bgPlane = new pc.Entity("Plane");
|
| 396 |
+
bgPlane.addComponent("model", { type: "plane" });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 397 |
|
| 398 |
+
bgMat = new pc.StandardMaterial();
|
| 399 |
+
bgMat.useLighting = false;
|
| 400 |
+
bgMat.emissive = new pc.Color(1, 1, 1);
|
| 401 |
+
bgMat.emissiveMap = rtTexture;
|
| 402 |
+
// si l'image apparaît inversée verticalement, décommente ces 2 lignes :
|
| 403 |
+
// bgMat.emissiveMapTiling.set(1, -1);
|
| 404 |
+
// bgMat.emissiveMapOffset.set(0, 1);
|
| 405 |
+
bgMat.cull = pc.CULLFACE_NONE; // visible des deux côtés (sécurité)
|
| 406 |
+
bgMat.update();
|
| 407 |
|
| 408 |
+
bgPlane.model.material = bgMat;
|
| 409 |
+
// placement plein écran devant la caméra (et parentée à la caméra)
|
| 410 |
+
fitFullscreenPlane(cameraEntity, bgPlane, 0.5);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 411 |
|
| 412 |
// ----- Lighting -----
|
| 413 |
const light = new pc.Entity("mainLight");
|
|
|
|
| 420 |
light.lookAt(0, 0, 0);
|
| 421 |
app.root.addChild(light);
|
| 422 |
|
| 423 |
+
// ----- Resize handling -----
|
| 424 |
+
const onAppResize = () => {
|
| 425 |
+
// resize RT à la nouvelle résolution
|
| 426 |
+
({ tex: rtTexture, rt: renderTarget } = createRT(app.graphicsDevice.width, app.graphicsDevice.height));
|
| 427 |
+
textureCamera.camera.renderTarget = renderTarget;
|
| 428 |
+
|
| 429 |
+
// rebrancher la nouvelle texture sur le matériau
|
| 430 |
+
bgMat.emissiveMap = rtTexture;
|
| 431 |
+
bgMat.update();
|
| 432 |
+
|
| 433 |
+
// s'assurer que la projection/cadre correspondent toujours
|
| 434 |
+
copyCam(cameraEntity, textureCamera);
|
| 435 |
+
// refaire le fit plein écran
|
| 436 |
+
fitFullscreenPlane(cameraEntity, bgPlane, 0.5);
|
| 437 |
+
};
|
| 438 |
+
app.on('resize', onAppResize);
|
| 439 |
+
|
| 440 |
app.resizeCanvas(viewerContainer.clientWidth, viewerContainer.clientHeight);
|
| 441 |
app.once('update', () => resetViewerCamera());
|
| 442 |
|