diff --git a/samples/ext/Sample_SetPlane.ts b/samples/ext/Sample_SetPlane.ts new file mode 100644 index 00000000..1d27ffa2 --- /dev/null +++ b/samples/ext/Sample_SetPlane.ts @@ -0,0 +1,242 @@ +import { + Engine3D, View3D, Scene3D, CameraUtil, AtmosphericComponent, webGPUContext, + HoverCameraController, Object3D, DirectLight, KelvinUtil, + MeshRenderer, + Object3DUtil, + Plane3D, + Vector3, + PointerEvent3D, + PostProcessingComponent, + ColliderComponent, + Matrix4, + Quaternion, + ComponentBase, + Time, + clamp, + GeometryBase, + LitMaterial, + Color, +} from "@orillusion/core"; + +import { GUIHelp } from "@orillusion/debug/GUIHelp"; +import { GUIUtil } from "@samples/utils/GUIUtil"; +import { setFrameDelay, setTimeDelay } from "../../src/util/DelayUtil"; +import { CCLQueryPost } from "./ccl/CCLQueryPost"; + +class LerpData { + quat: Quaternion = new Quaternion(); + y: number = 0; +} + +class SetPlaneAnimation extends ComponentBase { + + private _current = new LerpData() + private _target = new LerpData() + private _lerp = new LerpData(); + + private _remain: number = 0; + private _during: number = 0; + private _isBottom: boolean; + public playAnimation(pos: Vector3, normal: Vector3, during: number, isBottom?: boolean) { + this._isBottom = isBottom; + //计算所需要的旋转 + let matrix = Matrix4.helpMatrix.transformDir(normal, this._isBottom ? Vector3.DOWN : Vector3.UP); + let rotateQuat = new Quaternion(); + rotateQuat.fromMatrix(matrix); + + this._current.quat.copyFrom(this.transform.localRotQuat); + this._target.quat.multiply(rotateQuat, this._current.quat); + this._remain = this._during = during; + + this._current.y = this.transform.y; + this._target.y = -this.calcDeltaTransition(this.object3D, pos, this._target.quat); + } + + private localPos: Vector3; + private invMtrx: Matrix4; + private calcDeltaTransition(obj: Object3D, pos: Vector3, targetQuation: Quaternion) { + this.invMtrx ||= new Matrix4(); + this.localPos ||= new Vector3(); + this.invMtrx.copyFrom(obj.transform.worldMatrix).invert(); + this.invMtrx.transformPoint(pos, this.localPos); + + targetQuation.transformVector(this.localPos, this.localPos); + return this.localPos.y; + } + + public onUpdate(view?: View3D) { + this._remain -= Time.delta; + let t = this.getProgress(); + + this._lerp.quat.lerp(this._current.quat, this._target.quat, t); + this.object3D.localQuaternion = this._lerp.quat; + + this._lerp.y = this._current.y * (1 - t) + this._target.y * t; + this.transform.y = this._lerp.y; + + if (this._remain <= 0) { + this.object3D.removeComponent(SetPlaneAnimation); + } + } + + private getProgress() { + let t = 1 - clamp(this._remain / this._during, 0, 1); + t = t * 0.5 * Math.PI; + t = Math.sin(t); + return t; + } +} + +class Sample_SetPlane { + view: View3D; + scene: Scene3D; + private plane = new Plane3D(); + private isBottomPlane: boolean = true; + private cclPost: CCLQueryPost; + private postProcessing: PostProcessingComponent; + private cclColor: Color = new Color(0.6, 0.2, 0.2, 0.8); + async run() { + GUIHelp.init(); + + Engine3D.setting.shadow.shadowSize = 2048 + Engine3D.setting.shadow.shadowBound = 100; + Engine3D.setting.pick.enable = true; + Engine3D.setting.pick.mode = `pixel`; + + await Engine3D.init({ renderLoop: () => { this.loop() } }); + + this.scene = new Scene3D(); + let sky = this.scene.addComponent(AtmosphericComponent); + sky.sunY = 0.6; + + let mainCamera = CameraUtil.createCamera3DObject(this.scene, 'camera'); + mainCamera.perspective(60, webGPUContext.aspect, 1, 5000.0); + let ctrl = mainCamera.object3D.addComponent(HoverCameraController); + ctrl.setCamera(30, -15, 10); + + this.initScene(); + sky.relativeTransform = this.lightObj.transform; + + this.view = new View3D(); + this.view.scene = this.scene; + this.view.camera = mainCamera; + Engine3D.startRenderView(this.view); + + this.postProcessing = this.scene.getOrAddComponent(PostProcessingComponent); + + //确定要退出选择平面功能,可以移除Post,否则只需要将 cclPost.activePost设置为false即可。 + GUIHelp.addButton('Remove Post', () => { + if (this.cclPost) { + this.postProcessing.removePost(CCLQueryPost); + this.cclPost = null; + } + }); + + GUIHelp.addColor(this, 'cclColor').onChange(v => { + if (this.cclPost) { + this.cclPost.selectPlaneColor.copyFrom(this.cclColor); + } + }) + + let car = await Engine3D.res.loadGltf('gltfs/glb/PotionBottle.glb'); + + let geometry = car.getComponents(MeshRenderer)[0].geometry; + let model = this.initToothModel(geometry); + this.scene.addChild(model); + + this.registerEvents(); + } + + private getCCLPost() { + if (!this.cclPost) { + this.cclPost = this.postProcessing.addPost(CCLQueryPost); + this.cclPost.activePost = true; + this.cclPost.selectPlaneColor.copyFrom(this.cclColor); + } + return this.cclPost; + } + + private initToothModel(geometry: GeometryBase) { + let cube = new Object3D(); + + let meshRenderer = cube.addComponent(MeshRenderer); + cube.addComponent(ColliderComponent); + + meshRenderer.geometry = geometry; + meshRenderer.material = new LitMaterial(); + + GUIHelp.add(this, 'isBottomPlane'); + + return cube; + } + + private registerEvents() { + let pickFire = this.scene.view.pickFire; + this.scene.view.enablePick = true; + pickFire.addEventListener(PointerEvent3D.PICK_CLICK, this.onMousePick, this); + } + + private async onMousePick(e: PointerEvent3D) { + let obj = e.data.pick?.object3D; + if (obj == null) return; + let animation = obj.getComponent(SetPlaneAnimation); + if (animation) return; + let info = e.data.pickInfo; + if (!this.cclPost) { + this.cclPost = this.getCCLPost(); + //等待一段时间,否则在刚创建完post后,第一时间内,click的数据是之前取出来的buffer数据。 + //导致点击效果看不到 + await setTimeDelay(100); + } + this.cclPost.activePost = true; + this.displaySelectArea(info); + //等待一帧,供渲染 + await setFrameDelay(1); + //暂停引擎,否则cclPost卡顿导致应用不流畅 + Engine3D.pause(); + //等待200ms后,恢复渲染,并且将cclPost设置为失效状态(下次点击会再次恢复一下),相比重新构建一个post要优雅一些。 + await setTimeDelay(200); + this.cclPost.setPickData(null); + this.cclPost.activePost = false; + Engine3D.resume(); + //等待恢复渲染完毕的一帧之后,播放动画 + await setFrameDelay(1); + + //应用之前的点击结果播放动画 + let { worldPos, worldNormal, meshID, coord } = info; + let component = obj.addComponent(SetPlaneAnimation); + component.playAnimation(worldPos, worldNormal, 500, this.isBottomPlane); + this.cclPost.setPickData(null); + } + + private displaySelectArea(info) { + let { worldPos, worldNormal, meshID, coord } = info; + this.plane.fromNormalAndPoint(worldNormal, worldPos); + this.plane.d *= -1; + this.cclPost.setPickData(this.plane, coord.x, coord.y, meshID); + } + + + lightObj: Object3D; + async initScene() { + this.lightObj = new Object3D(); + this.lightObj.rotationX = 35; + this.lightObj.rotationY = 110; + this.lightObj.rotationZ = 0; + let lc = this.lightObj.addComponent(DirectLight); + lc.lightColor = KelvinUtil.color_temperature_to_rgb(5355); + lc.castShadow = true; + lc.intensity = 20; + lc.indirect = 1 + this.scene.addChild(this.lightObj); + GUIUtil.renderDirLight(lc, false); + + this.scene.addChild(Object3DUtil.GetSingleCube(400, 0.1, 400, 0.2, 0.2, 0.2)) + } + + loop() { + + } +} + +new Sample_SetPlane().run(); \ No newline at end of file diff --git a/samples/ext/ccl/CCLQueryPost.ts b/samples/ext/ccl/CCLQueryPost.ts new file mode 100644 index 00000000..797ee633 --- /dev/null +++ b/samples/ext/ccl/CCLQueryPost.ts @@ -0,0 +1,227 @@ +import { + View3D, + webGPUContext, + Plane3D, + PostBase, + Color, + ComputeShader, + GBufferFrame, + GPUContext, + GPUTextureFormat, + RTDescriptor, + RTFrame, + RendererPassState, + StorageGPUBuffer, + UniformGPUBuffer, + VirtualTexture, + WebGPUDescriptorCreator, +} from "@orillusion/core"; +import { CCL_Index } from "./ccl/CCL_Index"; +import { CCL_Blend } from "./ccl/CCL_Blend"; +import { CCL_BorderLinkClear } from "./ccl/CCL_BorderLinkClear"; +import { CCL_BorderLinkGen } from "./ccl/CCL_BorderLinkGen"; +import { CCL_BorderLinkRedirect } from "./ccl/CCL_BorderLinkRedirect"; +import { CCL_Partition } from "./ccl/CCL_Partition"; + +export class CCLQueryPost extends PostBase { + private outputTexture: VirtualTexture; + private rendererPassState: RendererPassState; + private indexCompute: ComputeShader; + private partitionCompute: ComputeShader; + private borderLinkClearCompute: ComputeShader; + private borderLinkGenCompute: ComputeShader; + private redirectLinkCompute: ComputeShader; + private blendCompute: ComputeShader; + private selectColorUniform: UniformGPUBuffer; + private cclUniformData: UniformGPUBuffer; + private cclUniformArray: Float32Array; + private cclBuffer: StorageGPUBuffer; + private borderLinkBuffer: StorageGPUBuffer; + private redrectLinkBuffer: StorageGPUBuffer; + private borderLinkAtomic: StorageGPUBuffer; + private rtFrame: RTFrame; + + private readonly GridCount = 64; + + public readonly selectPlaneColor: Color = new Color(0.6, 0.2, 0.2, 0.8); + private _activePost: boolean = true; + public get activePost(): boolean { + return this._activePost; + } + public set activePost(value: boolean) { + this._activePost = value; + } + + /** + * @internal + */ + onAttach(view: View3D,) { + } + /** + * @internal + */Render + onDetach(view: View3D,) { + } + + public setPickData(plane: Plane3D, coordX: number = -1, coordY: number = -1, meshID: number = -1): this { + this.cclUniformArray ||= new Float32Array(12); + if (!plane) this.cclUniformArray.fill(0); + else { + this.cclUniformArray[0] = plane.a; + this.cclUniformArray[1] = plane.b; + this.cclUniformArray[2] = plane.c; + this.cclUniformArray[3] = plane.d; + this.cclUniformArray[4] = coordX; + this.cclUniformArray[5] = coordY; + this.cclUniformArray[6] = meshID; + } + let [w, h] = webGPUContext.presentationSize; + this.cclUniformArray[7] = w; + this.cclUniformArray[8] = h; + this.cclUniformArray[9] = this.GridCount; + this.cclUniformArray[10] = this.activePost ? 1 : 0; + + this.cclUniformData.setFloat32Array('data', this.cclUniformArray); + return this; + } + + private createCompute() { + let rtFrame = GBufferFrame.getGBufferFrame("ColorPassGBuffer"); + this.cclUniformData = new UniformGPUBuffer(12); + + this.selectColorUniform = new UniformGPUBuffer(4); + this.selectColorUniform.setColor('data', this.selectPlaneColor); + + this.cclBuffer = new StorageGPUBuffer(this.outputTexture.width * this.outputTexture.height); + this.borderLinkBuffer = new StorageGPUBuffer(this.outputTexture.width * this.outputTexture.height * 2); + this.redrectLinkBuffer = new StorageGPUBuffer(this.outputTexture.width * this.outputTexture.height); + //index + this.indexCompute = new ComputeShader(CCL_Index); + this.indexCompute.setUniformBuffer('cclUniformData', this.cclUniformData); + this.indexCompute.setStorageBuffer('cclBuffer', this.cclBuffer); + this.indexCompute.setSamplerTexture('posTex', rtFrame.getPositionMap()); + this.indexCompute.setSamplerTexture('normalTex', rtFrame.getNormalMap()); + + //partition + this.partitionCompute = new ComputeShader(CCL_Partition); + this.partitionCompute.setUniformBuffer('cclUniformData', this.cclUniformData); + this.partitionCompute.setStorageBuffer('cclBuffer', this.cclBuffer); + //border clear + this.borderLinkClearCompute = new ComputeShader(CCL_BorderLinkClear); + this.borderLinkClearCompute.setUniformBuffer('cclUniformData', this.cclUniformData); + this.borderLinkClearCompute.setStorageBuffer('borderLinkBuffer', this.borderLinkBuffer); + this.borderLinkClearCompute.setStorageBuffer('labelRedirectBuffer', this.redrectLinkBuffer); + //border link + this.borderLinkGenCompute = new ComputeShader(CCL_BorderLinkGen); + this.borderLinkGenCompute.setStorageBuffer("borderLinkAtomic", this.borderLinkAtomic); + this.borderLinkGenCompute.setUniformBuffer('cclUniformData', this.cclUniformData); + this.borderLinkGenCompute.setStorageBuffer('cclBuffer', this.cclBuffer); + this.borderLinkGenCompute.setStorageBuffer('borderLinkBuffer', this.borderLinkBuffer); + //redirect + this.redirectLinkCompute = new ComputeShader(CCL_BorderLinkRedirect); + this.redirectLinkCompute.setUniformBuffer('cclUniformData', this.cclUniformData); + this.redirectLinkCompute.setStorageBuffer('borderLinkBuffer', this.borderLinkBuffer); + this.redirectLinkCompute.setStorageBuffer('labelRedirectBuffer', this.redrectLinkBuffer); + //blend + this.blendCompute = new ComputeShader(CCL_Blend); + this.blendCompute.setUniformBuffer('selectPlaneColor', this.selectColorUniform); + this.blendCompute.setUniformBuffer('cclUniformData', this.cclUniformData); + this.blendCompute.setStorageBuffer('cclBuffer', this.cclBuffer); + this.blendCompute.setStorageBuffer('labelRedirectBuffer', this.redrectLinkBuffer); + this.blendCompute.setSamplerTexture('colorTex', rtFrame.getColorMap()); + this.blendCompute.setStorageTexture(`outTex`, this.outputTexture); + } + + private createResource() { + this.initAtomicBuffer(); + + let [w, h] = webGPUContext.presentationSize; + this.outputTexture = new VirtualTexture(w, h, GPUTextureFormat.rgba16float, false, GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC | GPUTextureUsage.TEXTURE_BINDING); + this.outputTexture.name = 'cclTex'; + let cclDesc = new RTDescriptor(); + cclDesc.loadOp = `load`; + this.rtFrame = new RTFrame([this.outputTexture], [cclDesc]); + } + + protected initAtomicBuffer() { + this.borderLinkAtomic = new StorageGPUBuffer(4); + this.borderLinkAtomic.setUint32("linkIndex", 0.0); + this.borderLinkAtomic.setUint32("slot0", 0.0); + this.borderLinkAtomic.setUint32("slot1", 0.0); + this.borderLinkAtomic.setUint32("slot2", 0.0); + } + + activeComputes: ComputeShader[]; + deactiveComputes: ComputeShader[]; + + render(view: View3D, command: GPUCommandEncoder) { + if (!this.indexCompute) { + this.createResource(); + this.createCompute(); + this.onResize(); + + this.rendererPassState = WebGPUDescriptorCreator.createRendererPassState(this.rtFrame, null); + this.rendererPassState.label = "CCL"; + } + + if (!this.activeComputes) { + this.activeComputes = + [this.indexCompute, this.partitionCompute, + this.borderLinkClearCompute, this.borderLinkGenCompute, + this.redirectLinkCompute, this.blendCompute]; + + this.deactiveComputes = [this.blendCompute]; + } + this.selectColorUniform.setColor('data', this.selectPlaneColor); + this.selectColorUniform.apply(); + this.cclUniformData.apply(); + this.borderLinkAtomic.apply(); + if (this.activePost) { + GPUContext.computeCommand(command, this.activeComputes); + } else { + GPUContext.computeCommand(command, this.deactiveComputes); + + } + GPUContext.lastRenderPassState = this.rendererPassState; + } + + public onResize() { + let [w, h] = webGPUContext.presentationSize; + this.outputTexture.resize(w, h); + + this.cclBuffer.resizeBuffer(w * h); + this.borderLinkBuffer.resizeBuffer(w * h * 2); + this.redrectLinkBuffer.resizeBuffer(w * h); + + let fullWorkerSizeX = Math.ceil(w / 8); + let fullWorkerSizeY = Math.ceil(h / 8); + + this.indexCompute.workerSizeX = fullWorkerSizeX; + this.indexCompute.workerSizeY = fullWorkerSizeY; + this.indexCompute.workerSizeZ = 1; + + this.borderLinkClearCompute.workerSizeX = fullWorkerSizeX; + this.borderLinkClearCompute.workerSizeY = fullWorkerSizeY; + this.borderLinkClearCompute.workerSizeZ = 1; + + //partition grid 4*4 + const gridCount = this.GridCount; + this.partitionCompute.workerSizeX = Math.ceil(fullWorkerSizeX / gridCount); + this.partitionCompute.workerSizeY = Math.ceil(fullWorkerSizeY / gridCount); + this.partitionCompute.workerSizeZ = 1; + + this.borderLinkGenCompute.workerSizeX = Math.ceil(fullWorkerSizeX / gridCount); + this.borderLinkGenCompute.workerSizeY = Math.ceil(fullWorkerSizeY / gridCount); + this.borderLinkGenCompute.workerSizeZ = 1; + + this.redirectLinkCompute.workerSizeX = fullWorkerSizeX; + this.redirectLinkCompute.workerSizeY = fullWorkerSizeY; + this.redirectLinkCompute.workerSizeZ = 1; + + this.blendCompute.workerSizeX = fullWorkerSizeX; + this.blendCompute.workerSizeY = fullWorkerSizeY; + this.blendCompute.workerSizeZ = 1; + + this.setPickData(null); + } +} \ No newline at end of file diff --git a/samples/ext/ccl/ccl/CCL_Blend.ts b/samples/ext/ccl/ccl/CCL_Blend.ts new file mode 100644 index 00000000..92c06c2d --- /dev/null +++ b/samples/ext/ccl/ccl/CCL_Blend.ts @@ -0,0 +1,83 @@ +export let CCL_Blend: string = /*wgsl*/ ` + + struct CCLUniformStruct{ + plane:vec4, + coordX:f32, + coordY:f32, + meshID:f32, + imageWidth:f32, + imageHeight:f32, + gridCount:f32, + activePost:f32, + slot0:f32, + } + + @group(0) @binding(0) var selectPlaneColor: vec4; + @group(0) @binding(1) var cclUniformData: CCLUniformStruct; + @group(0) @binding(2) var cclBuffer : array; + @group(0) @binding(3) var labelRedirectBuffer : array; + @group(0) @binding(4) var colorTex : texture_2d; + @group(0) @binding(5) var outTex : texture_storage_2d; + + var texSize: vec2; + var fragCoord: vec2; + var wColor: vec4; + + @compute @workgroup_size( 8 , 8 , 1 ) + fn CsMain( @builtin(workgroup_id) workgroup_id : vec3 , @builtin(global_invocation_id) globalInvocation_id : vec3) + { + fragCoord = vec2( globalInvocation_id.xy ); + texSize = vec2(u32(cclUniformData.imageWidth), u32(cclUniformData.imageHeight)); + if(fragCoord.x >= i32(texSize.x) || fragCoord.y >= i32(texSize.y)){ + return; + } + wColor = textureLoad(colorTex, fragCoord, 0); + + if(cclUniformData.activePost > 0.5){ + let index = fragCoord.x + fragCoord.y * i32(texSize.x); + let pickCoordX = i32(round(cclUniformData.coordX)); + let pickCoordY = i32(round(cclUniformData.coordY)); + var pIndex = pickCoordX + pickCoordY * i32(texSize.x); + if(isLinked(pIndex, index)){ + var srcColor = wColor.xyz; + var destColor = selectPlaneColor.xyz; + destColor = mix(srcColor, destColor, selectPlaneColor.w); + + wColor.x = destColor.x; + wColor.y = destColor.y; + wColor.z = destColor.z; + } + } + textureStore(outTex, fragCoord , wColor); + } + + fn isLinked(pickCoordIdx:i32, currentCoordIdx:i32) -> bool{ + var pickLabel = i32(cclBuffer[pickCoordIdx]); + if(pickLabel < 0){ + return false; + }else{ + var currentLabel = i32(cclBuffer[currentCoordIdx]); + if(pickLabel == currentLabel){ + return true; + }else{ + if(currentLabel < 0){ + return false; + }else{ + let pickLabelRedirect = i32(labelRedirectBuffer[pickLabel]); + if(pickLabelRedirect >= 0){ + pickLabel = pickLabelRedirect; + } + let currentLabelRedirect = i32(labelRedirectBuffer[currentLabel]); + if(currentLabelRedirect >= 0){ + currentLabel = currentLabelRedirect; + } + + return pickLabel == currentLabel; + + } + } + } + } + + + ` \ No newline at end of file diff --git a/samples/ext/ccl/ccl/CCL_BorderLinkClear.ts b/samples/ext/ccl/ccl/CCL_BorderLinkClear.ts new file mode 100644 index 00000000..3225b22e --- /dev/null +++ b/samples/ext/ccl/ccl/CCL_BorderLinkClear.ts @@ -0,0 +1,39 @@ +export let CCL_BorderLinkClear: string = /*wgsl*/ ` + + struct CCLUniformStruct{ + plane:vec4, + coordX:f32, + coordY:f32, + meshID:f32, + imageWidth:f32, + imageHeight:f32, + gridCount:f32, + activePost:f32, + slot0:f32, + } + + @group(0) @binding(0) var cclUniformData: CCLUniformStruct; + @group(0) @binding(1) var borderLinkBuffer : array; + @group(0) @binding(2) var labelRedirectBuffer : array; + + var texSize: vec2; + var texSizeI32: vec2; + var fragCoord: vec2; + + @compute @workgroup_size( 8 , 8 , 1 ) + fn CsMain( @builtin(workgroup_id) workgroup_id : vec3 , @builtin(global_invocation_id) globalInvocation_id : vec3) + { + fragCoord = vec2( globalInvocation_id.xy ); + texSize = vec2(u32(cclUniformData.imageWidth), u32(cclUniformData.imageHeight)); + texSizeI32 = vec2( texSize ); + if(fragCoord.x >= i32(texSize.x) || fragCoord.y >= i32(texSize.y)){ + return; + } + let index = fragCoord.x + fragCoord.y * i32(texSize.x); + borderLinkBuffer[index * 2] = -1.0; + borderLinkBuffer[index * 2 + 1] = -1.0; + labelRedirectBuffer[index] = -1.0; + } + + + ` \ No newline at end of file diff --git a/samples/ext/ccl/ccl/CCL_BorderLinkGen.ts b/samples/ext/ccl/ccl/CCL_BorderLinkGen.ts new file mode 100644 index 00000000..18045ea3 --- /dev/null +++ b/samples/ext/ccl/ccl/CCL_BorderLinkGen.ts @@ -0,0 +1,99 @@ +export let CCL_BorderLinkGen: string = /*wgsl*/ ` + + struct CCLUniformStruct{ + plane:vec4, + coordX:f32, + coordY:f32, + meshID:f32, + imageWidth:f32, + imageHeight:f32, + gridCount:f32, + activePost:f32, + slot0:f32, + } + + struct BorderLinkAtomicStruct{ + linkIndex:atomic, + slot0:u32, + slot1:u32, + slot2:u32, + } + + @group(0) @binding(0) var cclUniformData: CCLUniformStruct; + @group(0) @binding(1) var borderLinkAtomic : BorderLinkAtomicStruct ; + @group(0) @binding(2) var cclBuffer : array; + @group(0) @binding(3) var borderLinkBuffer : array; + + var texSize: vec2; + var texSizeI32: vec2; + var fragCoord: vec2; + var gridCount: i32; + + @compute @workgroup_size( 8 , 8 , 1 ) + fn CsMain( @builtin(workgroup_id) workgroup_id : vec3 , @builtin(global_invocation_id) globalInvocation_id : vec3) + { + gridCount = i32(cclUniformData.gridCount); + fragCoord = vec2( globalInvocation_id.xy ) * gridCount; + texSize = vec2(u32(cclUniformData.imageWidth), u32(cclUniformData.imageHeight)); + texSizeI32 = vec2( texSize ); + if(fragCoord.x >= i32(texSize.x) || fragCoord.y >= i32(texSize.y)){ + return; + } + + if(fragCoord.x > 0){ + processLeftBorder(); + } + + if(fragCoord.y > 0){ + processTopBorder(); + } + + } + + fn processLeftBorder() + { + for(var i = 0; i < gridCount; i ++){ + let indexCurrent = getIndex(fragCoord.x, fragCoord.y + i); + let indexBorder = getIndex(fragCoord.x - 1, fragCoord.y + i); + tryLinkIndex(indexCurrent, indexBorder); + } + } + + fn processTopBorder() + { + for(var i = 0; i < gridCount; i ++){ + let indexCurrent = getIndex(fragCoord.x + i, fragCoord.y); + let indexBorder = getIndex(fragCoord.x + i, fragCoord.y - 1); + tryLinkIndex(indexCurrent, indexBorder); + } + } + + fn tryLinkIndex(index0:i32, index1:i32) + { + if(index0 > -1 && index1 > -1){ + let labelCurrent = cclBuffer[index0]; + let labelBorder = cclBuffer[index1]; + if(labelCurrent > -0.5 && labelBorder > -0.5 && labelCurrent != labelBorder){ + var fID = atomicAdd(&borderLinkAtomic.linkIndex, 1u); + fID *= 2u; + if(labelCurrent > labelBorder){ + borderLinkBuffer[fID] = labelCurrent; + borderLinkBuffer[fID + 1u] = labelBorder; + }else{ + borderLinkBuffer[fID] = labelBorder; + borderLinkBuffer[fID + 1u] = labelCurrent; + } + } + } + } + + fn getIndex(coordX:i32, coordY:i32) -> i32 + { + if(coordX < 0 || coordX >= texSizeI32.x || coordY < 0 || coordY >= texSizeI32.y){ + return -1; + } + return coordY * texSizeI32.x + coordX; + } + + + ` \ No newline at end of file diff --git a/samples/ext/ccl/ccl/CCL_BorderLinkRedirect.ts b/samples/ext/ccl/ccl/CCL_BorderLinkRedirect.ts new file mode 100644 index 00000000..0a17dd67 --- /dev/null +++ b/samples/ext/ccl/ccl/CCL_BorderLinkRedirect.ts @@ -0,0 +1,104 @@ +export let CCL_BorderLinkRedirect: string = /*wgsl*/ ` + + struct CCLUniformStruct{ + plane:vec4, + coordX:f32, + coordY:f32, + meshID:f32, + imageWidth:f32, + imageHeight:f32, + gridCount:f32, + activePost:f32, + slot0:f32, + } + + struct LinkMap + { + min:i32, + count:i32, + labels:array + } + + @group(0) @binding(0) var cclUniformData: CCLUniformStruct; + @group(0) @binding(1) var borderLinkBuffer : array; + @group(0) @binding(2) var labelRedirectBuffer : array; + + var texSize: vec2; + var texSizeI32: vec2; + var fragCoord: vec2; + + @compute @workgroup_size( 8, 8, 1 ) + fn CsMain( @builtin(workgroup_id) workgroup_id : vec3 , @builtin(global_invocation_id) globalInvocation_id : vec3) + { + fragCoord = vec2( globalInvocation_id.xy ); + texSize = vec2(u32(cclUniformData.imageWidth), u32(cclUniformData.imageHeight)); + texSizeI32 = vec2( texSize ); + if(fragCoord.x >= i32(texSize.x) || fragCoord.y >= i32(texSize.y)){ + return; + } + let index = fragCoord.x + fragCoord.y * i32(texSize.x); + + var lbMax = i32(borderLinkBuffer[index * 2]); + var lbMin = i32(borderLinkBuffer[index * 2 + 1]); + + if(lbMax != -1 && lbMin != -1){ + recordLinkLabel(lbMax, lbMin); + } + } + + fn recordLinkLabel(lbMax:i32, lbMin:i32) + { + var map: LinkMap; + map.min = lbMin; + map.count = 2; + map.labels[0] = lbMax; + map.labels[1] = lbMin; + // + let maxIndex = texSizeI32.x * texSizeI32.y; + var lastCount = 0; + + while(lastCount != map.count){ + lastCount = map.count; + for(var i = 0; i < maxIndex; i ++ ){ + var head = i32(borderLinkBuffer[i * 2]); + var tail = i32(borderLinkBuffer[i * 2 + 1]); + + if(head == -1 || tail == -1){ + break; + } + + //判断是否该链接关系在labels中 + var isHeadIn = false; + var isTailIn = false; + for(var j = 0; j < map.count; j ++){ + let lb = map.labels[j]; + + if(lb == head){ + isHeadIn = true; + } + if(lb == tail){ + isTailIn = true; + } + + if(isHeadIn && isTailIn){ + break; + } + } + + //发现有一个在已有数组里 + if(isHeadIn != isTailIn){ + if(isHeadIn){ + map.labels[map.count] = tail; + map.min = min(map.min, tail); + }else{ + map.labels[map.count] = head; + } + map.count = map.count + 1; + break; + } + } + } + labelRedirectBuffer[lbMax] = f32(map.min); + } + + ` \ No newline at end of file diff --git a/samples/ext/ccl/ccl/CCL_Index.ts b/samples/ext/ccl/ccl/CCL_Index.ts new file mode 100644 index 00000000..17c88bd2 --- /dev/null +++ b/samples/ext/ccl/ccl/CCL_Index.ts @@ -0,0 +1,63 @@ +export let CCL_Index: string = /*wgsl*/ ` + + struct CCLUniformStruct{ + plane:vec4, + coordX:f32, + coordY:f32, + meshID:f32, + imageWidth:f32, + imageHeight:f32, + gridCount:f32, + activePost:f32, + slot0:f32, + } + + @group(0) @binding(0) var cclUniformData: CCLUniformStruct; + @group(0) @binding(1) var cclBuffer : array; + @group(0) @binding(2) var posTex : texture_2d; + @group(0) @binding(3) var normalTex : texture_2d; + + var texSize: vec2; + var fragCoord: vec2; + var wPosition: vec3; + var wNormal: vec3; + var wColor: vec4; + + @compute @workgroup_size( 8 , 8 , 1 ) + fn CsMain( @builtin(workgroup_id) workgroup_id : vec3 , @builtin(global_invocation_id) globalInvocation_id : vec3) + { + fragCoord = vec2( globalInvocation_id.xy ); + texSize = textureDimensions(posTex).xy; + if(fragCoord.x >= i32(texSize.x) || fragCoord.y >= i32(texSize.y)){ + return; + } + let wn = textureLoad(normalTex, fragCoord, 0); + var label:f32 = -1.0; + let index = fragCoord.x + fragCoord.y * i32(texSize.x); + if(wn.w < 0.5){//sky + + }else{ + let wp = textureLoad(posTex, fragCoord, 0); + if(round(wp.w) == round(cclUniformData.meshID)){ + wPosition = wp.xyz; + wNormal = normalize(vec3(wn.xyz) * 255.0 - 127.0); + let plane = cclUniformData.plane; + let isAtPlaneBool = length(plane.xyz) > 0.1 && isAtPlane(plane); + if(isAtPlaneBool){ + label = f32(index); + } + } + } + + cclBuffer[index] = label; + } + + fn isAtPlane(plane:vec4) -> bool{ + let pos = wPosition; + let normal = wNormal; + let dotValue1 = dot( plane.xyz, pos ); + let dotValue2 = dot(plane.xyz, normal); + return abs(dotValue1 - plane.w) < 0.5 && dotValue2 > 0.95; + } + + ` \ No newline at end of file diff --git a/samples/ext/ccl/ccl/CCL_Partition.ts b/samples/ext/ccl/ccl/CCL_Partition.ts new file mode 100644 index 00000000..205b2195 --- /dev/null +++ b/samples/ext/ccl/ccl/CCL_Partition.ts @@ -0,0 +1,106 @@ +export let CCL_Partition: string = /*wgsl*/ ` + + struct CCLUniformStruct{ + plane:vec4, + coordX:f32, + coordY:f32, + meshID:f32, + imageWidth:f32, + imageHeight:f32, + gridCount:f32, + activePost:f32, + slot0:f32, + } + + @group(0) @binding(0) var cclUniformData: CCLUniformStruct; + @group(0) @binding(1) var cclBuffer : array; + + var texSize: vec2; + var texSizeI32: vec2; + var fragCoord: vec2; + var gridCount: i32; + + @compute @workgroup_size( 8 , 8 , 1 ) + fn CsMain( @builtin(workgroup_id) workgroup_id : vec3 , @builtin(global_invocation_id) globalInvocation_id : vec3) + { + gridCount = i32(cclUniformData.gridCount); + fragCoord = vec2( globalInvocation_id.xy ) * gridCount; + texSize = vec2(u32(cclUniformData.imageWidth), u32(cclUniformData.imageHeight)); + texSizeI32 = vec2( texSize ); + if(fragCoord.x >= i32(texSize.x) || fragCoord.y >= i32(texSize.y)){ + return; + } + + var retCount:u32 = 1u; + while(retCount > 0u){ + retCount = loopAndLabelPartition(); + } + } + + + fn loopAndLabelPartition() -> u32 + { + var retCount = 0u; + var tempIndex:i32 = 0; + var coord:vec2; + for(var y:i32 = 0; y < gridCount; y ++){ + for(var x:i32 = 0; x < gridCount; x ++){ + coord.x = fragCoord.x + x; + coord.y = fragCoord.y + y; + tempIndex = getIndex(coord.x, coord.y); + if(tempIndex > -1){ + let currentLabel = cclBuffer[tempIndex]; + if(currentLabel > -0.5){ + let nLabel = getMinNeighborLabel(currentLabel, coord, x, y); + if(nLabel != currentLabel){ + cclBuffer[tempIndex] = nLabel; + retCount ++; + } + } + } + } + } + return retCount; + } + + fn getIndex(coordX:i32, coordY:i32) -> i32 + { + if(coordX < 0 || coordX >= texSizeI32.x || coordY < 0 || coordY >= texSizeI32.y){ + return -1; + } + return coordY * texSizeI32.x + coordX; + } + + fn getMinNeighborLabel(current:f32, coord:vec2, lcX:i32, lcY:i32) -> f32 + { + var targetLabel = current; + var indexVec4:vec4 = vec4(-1, -1, -1, -1); + + if(lcX > 0){ + indexVec4.x = getIndex(coord.x - 1, coord.y);//left + } + if(lcX < gridCount - 1){ + indexVec4.y = getIndex(coord.x + 1, coord.y);//right + } + if(lcY > 0){ + indexVec4.z = getIndex(coord.x, coord.y - 1);//top + } + if(lcY < gridCount - 1){ + indexVec4.w = getIndex(coord.x, coord.y + 1);//bottom + } + + for(var i:u32 = 0u; i < 4u; i ++){ + let index = indexVec4[i]; + if(index > -1){ + var label = cclBuffer[index]; + if(label > -0.5 && label < targetLabel){ + targetLabel = label; + } + } + } + + return targetLabel; + } + + + ` \ No newline at end of file diff --git a/src/assets/shader/compute/Picker_cs.ts b/src/assets/shader/compute/Picker_cs.ts index cc502e70..6b4ea22f 100644 --- a/src/assets/shader/compute/Picker_cs.ts +++ b/src/assets/shader/compute/Picker_cs.ts @@ -7,31 +7,45 @@ export let Picker_cs: string = /*wgsl*/ ` pick_meshID:f32, pick_meshID2:f32, pick_UV:vec2, + + //4 5 6 7 pick_Position:vec4, + //8 9 10 11 pick_Normal:vec4, + //12 13 14 15 pick_Tangent:vec4, + + //16 17 + pick_Coord:vec2, + v4:vec2, + + v5:vec4, + v6:vec4, + v7:vec4 } //@group(0) @binding(0) var globalUniform: GlobalUniform; @group(0) @binding(1) var outBuffer: PickResult; - @group(0) @binding(2) var visibleMap : texture_2d; + @group(0) @binding(2) var positionMap : texture_2d; + @group(0) @binding(3) var normalMap : texture_2d; @compute @workgroup_size( 1 ) fn CsMain( @builtin(workgroup_id) workgroup_id : vec3 , @builtin(global_invocation_id) globalInvocation_id : vec3) { var result:PickResult ; - // result.pick_meshID - let texSize = textureDimensions(visibleMap).xy; + let texSize = textureDimensions(positionMap).xy; let screenPoint = vec2(globalUniform.mouseX/globalUniform.windowWidth,globalUniform.mouseY/globalUniform.windowHeight); let mouseUV = screenPoint * vec2(texSize.xy); - let info = textureLoad(visibleMap, vec2(mouseUV) , 0); + let pos = textureLoad(positionMap, vec2(mouseUV) , 0); + let normal = textureLoad(normalMap, vec2(mouseUV) , 0); - outBuffer.pick_meshID = f32(info.w) ; - outBuffer.pick_meshID2 = f32(info.w) ; + outBuffer.pick_meshID = f32(pos.w) ; + outBuffer.pick_meshID2 = f32(pos.w) ; outBuffer.pick_Tangent = vec4(2.0,2.0,2.0,2.0) ; outBuffer.pick_UV = vec2(globalUniform.mouseX,globalUniform.mouseY) ; - outBuffer.pick_Position = vec4(info.xyzw) ; - outBuffer.pick_Normal = vec4(info.xyzw) ; - } + outBuffer.pick_Position = pos; + outBuffer.pick_Normal = normal; + outBuffer.pick_Coord = mouseUV; +} ` diff --git a/src/index.ts b/src/index.ts index 900330d9..1f60c23f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -590,6 +590,7 @@ export * from "./util/BoundUtil" export * from "./util/BytesArray" export * from "./util/CameraUtil" export * from "./util/Convert" +export * from "./util/DelayUtil" export * from "./util/GeometryUtil" export * from "./util/Global" export * from "./util/KelvinUtil" diff --git a/src/io/PickFire.ts b/src/io/PickFire.ts index 78c8143b..39771209 100644 --- a/src/io/PickFire.ts +++ b/src/io/PickFire.ts @@ -140,6 +140,8 @@ export class PickFire extends CEventDispatcher { worldPos: this._pickCompute.getPickWorldPosition(), screenUv: this._pickCompute.getPickScreenUV(), meshID: this._pickCompute.getPickMeshID(), + worldNormal: this._pickCompute.getPickWorldNormal(), + coord: this._pickCompute.getPickCoord() }; } @@ -148,11 +150,12 @@ export class PickFire extends CEventDispatcher { this._mouseCode = e.mouseCode; this.pick(this._view.camera); let target = this.findNearestObj(this._interestList, this._view.camera); + this._mouseMove.target = target?.object3D; + this._mouseMove.ctrlKey = e.ctrlKey; + let pickInfo = target ? this.getPickInfo() : null; + this._mouseMove.data = { pick: target, pickInfo, mouseCode: this._mouseCode }; + this.dispatchEvent(this._mouseMove); if (target) { - this._mouseMove.target = target.object3D; - this._mouseMove.ctrlKey = e.ctrlKey; - this._mouseMove.data = { pick: target, pickInfo: this.getPickInfo(), mouseCode: this._mouseCode }; - this.dispatchEvent(this._mouseMove); if (target.object3D.containEventListener(PointerEvent3D.PICK_MOVE)) { target.object3D.dispatchEvent(this._mouseMove); } diff --git a/src/io/picker/PickCompute.ts b/src/io/picker/PickCompute.ts index 5b167826..b90875e1 100644 --- a/src/io/picker/PickCompute.ts +++ b/src/io/picker/PickCompute.ts @@ -22,7 +22,8 @@ export class PickCompute { this._outBuffer = new ComputeGPUBuffer(32); this._computeShader.setStorageBuffer('outBuffer', this._outBuffer); - this._computeShader.setSamplerTexture('visibleMap', rtFrame.getPositionMap()); + this._computeShader.setSamplerTexture('positionMap', rtFrame.getPositionMap()); + this._computeShader.setSamplerTexture('normalMap', rtFrame.getNormalMap()); } compute(view: View3D) { @@ -57,6 +58,19 @@ export class PickCompute { return target; } + /** + * Returns world position of pick result + * @returns + */ + public getPickWorldNormal(target?: Vector3): Vector3 { + target ||= new Vector3(); + var x = this._outBuffer.outFloat32Array[8]; + var y = this._outBuffer.outFloat32Array[9]; + var z = this._outBuffer.outFloat32Array[10]; + target.set(x * 255 - 127, y * 255 - 127, z * 255 - 127).normalize(); + return target; + } + /** * Returns screen coord of mouse * @returns @@ -68,4 +82,12 @@ export class PickCompute { target.set(x, y); return target; } + + public getPickCoord(target?: Vector2): Vector2 { + target ||= new Vector2(); + var x = this._outBuffer.outFloat32Array[16]; + var y = this._outBuffer.outFloat32Array[17]; + target.set(x, y); + return target; + } } diff --git a/src/math/Matrix4.ts b/src/math/Matrix4.ts index 9cc504b5..559d1ad9 100644 --- a/src/math/Matrix4.ts +++ b/src/math/Matrix4.ts @@ -741,7 +741,7 @@ export class Matrix4 { * @param toDirection second direction * @version Orillusion3D 0.5.1 */ - public transformDir(fromDirection: Vector3, toDirection: Vector3) { + public transformDir(fromDirection: Vector3, toDirection: Vector3): this { let data = this.rawData; let EPSILON: number = 0.000001; @@ -751,7 +751,7 @@ export class Matrix4 { if (e > 1.0 - EPSILON) { this.identity(); - } else if (3 < -1.0 + EPSILON) { + } else if (e < -1.0 + EPSILON) { let up: Vector3 = Vector3.HELP_1; let left: Vector3 = Vector3.HELP_2; // let invLen: number = 0; @@ -855,6 +855,8 @@ export class Matrix4 { data[11] = 0; data[15] = 1; } + + return this; } /** diff --git a/src/util/DelayUtil.ts b/src/util/DelayUtil.ts new file mode 100644 index 00000000..261a773a --- /dev/null +++ b/src/util/DelayUtil.ts @@ -0,0 +1,26 @@ +export let setTimeDelay = async function (ms: number) { + + return new Promise((resolve, reject) => { + setTimeout(() => { + resolve(true); + }, ms); + }) + +}; + + +export let setFrameDelay = async function (frame: number) { + return new Promise((resolve, reject) => { + frame = Math.max(1, frame); + let callback = function () { + if (frame < 0) { + resolve(true); + } else { + requestAnimationFrame(callback); + } + frame--; + } + requestAnimationFrame(callback); + }) + +}; \ No newline at end of file