forked from natuka/web.puabadge.com
修改:优化手机上效果图点云显示
This commit is contained in:
1 parent
17908ab309
commit
605bd894c5
4 files changed
+67
-10
No files matched your search
@@ -64,8 +64,8 @@ let renderer: any = null
|
|||||||
let sm: any = null
|
let sm: any = null
|
||||||
let raf = 0
|
let raf = 0
|
||||||
let lastTs = 0
|
let lastTs = 0
|
||||||
let cssW = 320
|
let cssW = 340
|
||||||
let cssH = 320
|
let cssH = 340
|
||||||
let logoFromCache = false
|
let logoFromCache = false
|
||||||
let resultKey = ''
|
let resultKey = ''
|
||||||
|
|
||||||
@@ -186,12 +186,23 @@ function preloadResult(url: string) {
|
|||||||
if (!url || !renderer || !sm) return
|
if (!url || !renderer || !sm) return
|
||||||
resultKey = url
|
resultKey = url
|
||||||
sm.setResultPreloadStatus('loading')
|
sm.setResultPreloadStatus('loading')
|
||||||
|
// 效果图保留完整画面做点云(去背景会在手机上采空,表现为「没有效果图点云」)
|
||||||
renderer.textures
|
renderer.textures
|
||||||
.load(url, { removeBackground: true })
|
.load(url, { removeBackground: false })
|
||||||
.then((entry: any) => {
|
.then((entry: any) => {
|
||||||
if (destroyed || !sm) return
|
if (destroyed || !sm) return
|
||||||
|
if (!entry || !entry.pointCloudBuffer) {
|
||||||
|
console.error('[ImagePointCloud] result cloud buffer missing')
|
||||||
|
sm.setResultPreloadStatus('error')
|
||||||
|
return
|
||||||
|
}
|
||||||
renderer.setResultTexture(entry)
|
renderer.setResultTexture(entry)
|
||||||
sm.setResultPreloadStatus('ready')
|
sm.setResultPreloadStatus('ready')
|
||||||
|
// 若状态机已进入 RESULT_*(异步晚到),立刻切到效果图点云
|
||||||
|
const phase = String(sm.getPhase?.() || '')
|
||||||
|
if (phase.startsWith('RESULT')) {
|
||||||
|
renderer.transitionToCloud(entry)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch((err: any) => {
|
.catch((err: any) => {
|
||||||
console.error('[ImagePointCloud] result preload fail', err)
|
console.error('[ImagePointCloud] result preload fail', err)
|
||||||
@@ -310,11 +321,15 @@ function init() {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onSwitchToResult: () => {
|
onSwitchToResult: () => {
|
||||||
const entry = renderer.textures.get(resultKey || props.resultUrl)
|
const key = resultKey || props.resultUrl
|
||||||
|
const entry = key ? renderer.textures.get(key) : null
|
||||||
if (entry && entry.pointCloudBuffer) {
|
if (entry && entry.pointCloudBuffer) {
|
||||||
renderer.transitionToCloud(entry)
|
renderer.transitionToCloud(entry)
|
||||||
renderer.setResultTexture(entry)
|
renderer.setResultTexture(entry)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
// 缓冲晚到时由 preloadResult 成功回调补切
|
||||||
|
if (key) preloadResult(key)
|
||||||
},
|
},
|
||||||
onHandoff: () => emit('handoff'),
|
onHandoff: () => emit('handoff'),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -124,6 +124,8 @@ function createStateMachine(hooks) {
|
|||||||
phase !== PHASE.RESULT_PRELOADING &&
|
phase !== PHASE.RESULT_PRELOADING &&
|
||||||
phase !== PHASE.SUBJECT_DETECTING &&
|
phase !== PHASE.SUBJECT_DETECTING &&
|
||||||
phase !== PHASE.SUBJECT_OUTLINE &&
|
phase !== PHASE.SUBJECT_OUTLINE &&
|
||||||
|
phase !== PHASE.SUBJECT_CLOUD_GRAY &&
|
||||||
|
phase !== PHASE.SUBJECT_CLOUD_COLORIZE &&
|
||||||
phase !== PHASE.SUBJECT_CLOUD_COLOR &&
|
phase !== PHASE.SUBJECT_CLOUD_COLOR &&
|
||||||
phase !== PHASE.SUBJECT_POINT_CLOUD
|
phase !== PHASE.SUBJECT_POINT_CLOUD
|
||||||
) {
|
) {
|
||||||
@@ -292,6 +294,7 @@ function createStateMachine(hooks) {
|
|||||||
if (!morphing) assembly = 1
|
if (!morphing) assembly = 1
|
||||||
subjectProgress = 1
|
subjectProgress = 1
|
||||||
outlineProgress = Math.max(0.35, 1 - Math.min(1, elapsed / 2400) * 0.55)
|
outlineProgress = Math.max(0.35, 1 - Math.min(1, elapsed / 2400) * 0.55)
|
||||||
|
if (tryStartResultTransition()) return getUniforms()
|
||||||
if (!morphing && elapsed >= TIMING.SUBJECT_GRAY_MS) {
|
if (!morphing && elapsed >= TIMING.SUBJECT_GRAY_MS) {
|
||||||
enter(PHASE.SUBJECT_CLOUD_COLORIZE)
|
enter(PHASE.SUBJECT_CLOUD_COLORIZE)
|
||||||
}
|
}
|
||||||
@@ -307,6 +310,7 @@ function createStateMachine(hooks) {
|
|||||||
if (!morphing) assembly = 1
|
if (!morphing) assembly = 1
|
||||||
subjectProgress = 1
|
subjectProgress = 1
|
||||||
outlineProgress = 0.35
|
outlineProgress = 0.35
|
||||||
|
if (tryStartResultTransition()) return getUniforms()
|
||||||
if (t >= 1) {
|
if (t >= 1) {
|
||||||
colorProgress = 1
|
colorProgress = 1
|
||||||
enter(PHASE.SUBJECT_CLOUD_COLOR)
|
enter(PHASE.SUBJECT_CLOUD_COLOR)
|
||||||
|
|||||||
@@ -115,7 +115,8 @@ function createTextureManager(gl, canvas, scaleCanvas, viewportW, viewportH) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 加载图片并严格在 onload 之后再交给后续逻辑(避免 TMA 未定义行为告警)
|
* 加载图片并严格在 onload 之后再交给后续逻辑。
|
||||||
|
* http(s) 优先 fetch→blob,避免手机 WebView 跨域导致 getImageData 采点失败。
|
||||||
*/
|
*/
|
||||||
function loadImage(src) {
|
function loadImage(src) {
|
||||||
return new Promise(function (resolve, reject) {
|
return new Promise(function (resolve, reject) {
|
||||||
@@ -123,10 +124,11 @@ function createTextureManager(gl, canvas, scaleCanvas, viewportW, viewportH) {
|
|||||||
reject(new Error('empty src'))
|
reject(new Error('empty src'))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function bindImage(imgSrc, revokeUrl) {
|
||||||
let settled = false
|
let settled = false
|
||||||
const img = new Image()
|
const img = new Image()
|
||||||
// 跨域效果图需 CORS,否则无法 getImageData 采样
|
if (/^https?:\/\//i.test(imgSrc) && imgSrc.indexOf('blob:') !== 0) {
|
||||||
if (/^https?:\/\//i.test(src)) {
|
|
||||||
img.crossOrigin = 'anonymous'
|
img.crossOrigin = 'anonymous'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,23 +140,57 @@ function createTextureManager(gl, canvas, scaleCanvas, viewportW, viewportH) {
|
|||||||
const h = img.height || 0
|
const h = img.height || 0
|
||||||
if (!w || !h) {
|
if (!w || !h) {
|
||||||
settled = true
|
settled = true
|
||||||
|
if (revokeUrl) {
|
||||||
|
try { URL.revokeObjectURL(revokeUrl) } catch (e) {}
|
||||||
|
}
|
||||||
reject(new Error('image empty after onload'))
|
reject(new Error('image empty after onload'))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
settled = true
|
settled = true
|
||||||
resolve({ image: img, path: src, width: w, height: h })
|
resolve({
|
||||||
|
image: img,
|
||||||
|
path: src,
|
||||||
|
width: w,
|
||||||
|
height: h,
|
||||||
|
revokeUrl: revokeUrl || '',
|
||||||
|
})
|
||||||
}, 0)
|
}, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
function finishErr(err) {
|
function finishErr(err) {
|
||||||
if (settled) return
|
if (settled) return
|
||||||
settled = true
|
settled = true
|
||||||
|
if (revokeUrl) {
|
||||||
|
try { URL.revokeObjectURL(revokeUrl) } catch (e) {}
|
||||||
|
}
|
||||||
reject(err || new Error('image load fail'))
|
reject(err || new Error('image load fail'))
|
||||||
}
|
}
|
||||||
|
|
||||||
img.onload = finishOk
|
img.onload = finishOk
|
||||||
img.onerror = finishErr
|
img.onerror = finishErr
|
||||||
img.src = src
|
img.src = imgSrc
|
||||||
|
}
|
||||||
|
|
||||||
|
// 远程图:先 blob 同源化,再采样(手机端效果图点云依赖此路径)
|
||||||
|
if (/^https?:\/\//i.test(src) && typeof fetch === 'function') {
|
||||||
|
fetch(src, { mode: 'cors', credentials: 'omit', cache: 'force-cache' })
|
||||||
|
.then(function (res) {
|
||||||
|
if (!res || !res.ok) throw new Error('fetch image http ' + (res && res.status))
|
||||||
|
return res.blob()
|
||||||
|
})
|
||||||
|
.then(function (blob) {
|
||||||
|
if (!blob || !blob.size) throw new Error('empty image blob')
|
||||||
|
const objUrl = URL.createObjectURL(blob)
|
||||||
|
bindImage(objUrl, objUrl)
|
||||||
|
})
|
||||||
|
.catch(function () {
|
||||||
|
// CORS 拉流失败时退回 <img crossOrigin>
|
||||||
|
bindImage(src, null)
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bindImage(src, null)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -333,10 +333,12 @@ function pollStatus() {
|
|||||||
imageId.value = parsed.imageId
|
imageId.value = parsed.imageId
|
||||||
sessionStorage.setItem('douyin_image_id', parsed.imageId)
|
sessionStorage.setItem('douyin_image_id', parsed.imageId)
|
||||||
}
|
}
|
||||||
|
// 不再 10s 强切预览:会掐掉 RESULT_CLOUD_* 效果图点云段
|
||||||
|
// 正常走点云状态机 handoff;仅作兜底(给足灰/彩点云播放时间)
|
||||||
if (fadeTimer) clearTimeout(fadeTimer)
|
if (fadeTimer) clearTimeout(fadeTimer)
|
||||||
fadeTimer = setTimeout(() => {
|
fadeTimer = setTimeout(() => {
|
||||||
if (!handoffDone) enterResultView()
|
if (!handoffDone) enterResultView()
|
||||||
}, 10000)
|
}, 60000)
|
||||||
if (pollTimer) {
|
if (pollTimer) {
|
||||||
clearInterval(pollTimer)
|
clearInterval(pollTimer)
|
||||||
pollTimer = null
|
pollTimer = null
|
||||||
|
|||||||
Reference in new issue
Block a user