diff --git a/Build.fs b/Build.fs index 29563925..785822da 100644 --- a/Build.fs +++ b/Build.fs @@ -237,7 +237,7 @@ let test = """let viewerVersion = "3.1.3" """ res *) -let aardiumVersion = "2.0.5" +let aardiumVersion = "2.1.1" //let versions = getInstalledPackageVersions() //match Map.tryFind "Aardium" versions with //| Some v -> v @@ -433,7 +433,7 @@ Target.create "Publish" (fun _ -> } ) - // snapshots + //// snapshots "src/PRo3D.Snapshots/PRo3D.Snapshots.fsproj" |> DotNet.publish (fun o -> { o with Framework = Some "net6.0" @@ -496,7 +496,7 @@ Target.create "Publish" (fun _ -> Shell.copyDir (Path.Combine(target, "mac-x64", "tools")) (Path.Combine(tempPath, "tools")) (fun _ -> true) Shell.copyDir (Path.Combine(target, "win-x64", "tools")) (Path.Combine(tempPath, "tools")) (fun _ -> true) - File.Move("bin/publish/win-x64/PRo3D.Viewer.exe", sprintf "bin/publish/win-x64/PRo3D.Viewer.%s.exe" notes.NugetVersion) + //File.Move("bin/publish/win-x64/PRo3D.Viewer.exe", sprintf "bin/publish/win-x64/PRo3D.Viewer.%s.exe" notes.NugetVersion) ) "Credits" ==> "Publish" |> ignore diff --git a/src/PRo3D.Core/Drawing/Drawing-App.fs b/src/PRo3D.Core/Drawing/Drawing-App.fs index 4b40ee79..631c7cf1 100644 --- a/src/PRo3D.Core/Drawing/Drawing-App.fs +++ b/src/PRo3D.Core/Drawing/Drawing-App.fs @@ -636,17 +636,17 @@ module DrawingApp = pickingTolerance = msmallConfig.getPickingTolerance mbigConfig } - let annoSet = + let labels = model.annotations.flat - |> AMap.choose (fun _ y -> y |> tryToAnnotation) // TODO v5: here we collapsed some avals - check correctness - |> AMap.toASet - - let labels = - annoSet - |> ASet.map(fun (_,a) -> - Sg.finishedAnnotationText a config view - ) - |> Sg.set + |> AMap.toASetValues + |> ASet.chooseA (fun anno -> + match anno |> tryToAnnotation with + | None -> AVal.constant None + | Some v -> + Sg.shouldTextBeRendered v + |> AVal.map (function | true -> Some (Sg.drawText view config v) | _ -> None) + ) + |> Sg.set labels diff --git a/src/PRo3D.Core/Drawing/Drawing.Sg.fs b/src/PRo3D.Core/Drawing/Drawing.Sg.fs index 6e0b7fae..c884ee60 100644 --- a/src/PRo3D.Core/Drawing/Drawing.Sg.fs +++ b/src/PRo3D.Core/Drawing/Drawing.Sg.fs @@ -413,15 +413,11 @@ module Sg = dotsAndText ] |> optional anno.visible - let finishedAnnotationText - (anno : AdaptiveAnnotation) - (config : innerViewConfig) - (view : aval) = - - anno.text - |> AVal.map3 (fun show visible text -> (String.IsNullOrEmpty text) || (show && visible) ) anno.showText anno.visible - |> optionalSet (drawText view config anno) - |> Sg.set + + let shouldTextBeRendered (anno : AdaptiveAnnotation) = + (anno.text, anno.visible, anno.showText) + |||> AVal.map3 (fun text visible show -> show && visible && not (String.IsNullOrEmpty text)) + let finishedAnnotation (anno : AdaptiveAnnotation) diff --git a/src/PRo3D.Core/Surface/Surface.Sg.fs b/src/PRo3D.Core/Surface/Surface.Sg.fs index 2b0692fc..3277cd25 100644 --- a/src/PRo3D.Core/Surface/Surface.Sg.fs +++ b/src/PRo3D.Core/Surface/Surface.Sg.fs @@ -30,6 +30,7 @@ open Aardvark.GeoSpatial.Opc.PatchLod open Aardvark.GeoSpatial.Opc.Load open OpcViewer.Base open Aardvark.Rendering.Text +open Aardvark.Geometry module FootprintSg = open Aardvark.SceneGraph.Sg @@ -147,6 +148,129 @@ module Sg = log area > 1.0 - (log p.factor) * 1.2 + module Helper = + + let intersectBox' (b : Box3d) (r : FastRay3d) = + let mutable tmin = -infinity + let mutable tmax = infinity + if r.Intersects(b, &tmin, &tmax) then + Some (tmin, tmax) + else + None + + let intersectBox (b : Box3d) (r : Ray3d) = + FastRay3d r |> intersectBox' b + + + + // hs: not sure what the original intention was, but was obviusly wrong (but worked reasonably well for most scenes) + // this this version removed obvious problems but is still much worse than the reworked on reworkedLoD + let cleanedOldLegacyLoD + (preTrafo : Trafo3d) + (self : AdaptiveToken) + (viewTrafo : aval) + (projTrafo : aval) + (renderPatch : Aardvark.GeoSpatial.Opc.PatchLod.RenderPatch) + (lodParams : aval) + (isActive : aval) + = + + let isRenderingActive = isActive.GetValue self + if isRenderingActive then + let lodParams = lodParams.GetValue self + let viewTrafo = viewTrafo.GetValue self + let model = preTrafo * renderPatch.trafo.GetValue self + let proj = projTrafo.GetValue self + let viewProj = viewTrafo * proj + + let globalBBModelSpace = renderPatch.info.LocalBoundingBox.Transformed(model) + let cornersNdc = globalBBModelSpace.ComputeCorners() |> Array.map viewProj.TransformPosProj + let boundsNdc = Box3d(cornersNdc) + if Box3d(-V3d.IIO, V3d.III).Intersects(boundsNdc) then + let campPos = viewTrafo.Backward.C3.XYZ + let bb = renderPatch.info.GlobalBoundingBox.Transformed(lodParams.trafo) + let closest = bb.GetClosestPointOn(campPos) + let dist = (closest - campPos).Length + + let unitPxSize = (lodParams.frustum.right - lodParams.frustum.left) / (float lodParams.size.X * 0.5) + let px = (0.1 * renderPatch.triangleSize) / (pow dist 1.2) // (pow dist 1.2) // (added pow 1.2 here... discuss) + + // Log.warn "%f to %f - avgSize: %f" px (unitPxSize * lodParams.factor) p.triangleSize + px > unitPxSize * (exp lodParams.factor) + else + false + else + false + + let reworkedLoD + (preTrafo : Trafo3d) + (intersect : ValueOption ValueOption>) + (self : AdaptiveToken) + (viewTrafo : aval) + (projTrafo : aval) + (renderPatch : Aardvark.GeoSpatial.Opc.PatchLod.RenderPatch) + (lodParams : aval) + (isActive : aval) = + + let renderingActive = isActive.GetValue self + if not renderingActive then false + else + let model = preTrafo * renderPatch.trafo.GetValue self + let view = viewTrafo.GetValue self + let proj = projTrafo.GetValue self + let p = lodParams.GetValue self + let viewProj = view * proj + + let globalBBModelSpace = renderPatch.info.LocalBoundingBox.Transformed(model) + let cornersNdc = globalBBModelSpace.ComputeCorners() |> Array.map viewProj.TransformPosProj + let boundsNdc = Box3d(cornersNdc) + if Box3d(-V3d.IIO, V3d.III).Intersects(boundsNdc) then + // if we have a potential hit, also use the hold hacky one, if both (the new one and the old one) agree on going deeper, let's do it + // the situation is rather complex since many special cases (huge bounding boxes, inhomogenous point densities etc) + let legacyDecider = lodDeciderMars preTrafo self viewTrafo projTrafo renderPatch lodParams isActive + + let camPos = view.Backward.C3.XYZ + let ray = Ray3d(view.Backward.C3.XYZ, -view.Backward.C2.XYZ) + let fastRay = FastRay3d(ray) + let referencePoint = + match Helper.intersectBox' globalBBModelSpace fastRay with + | Some (tmin,tmax) -> + match intersect with + | ValueSome intersectionFunction -> + match intersectionFunction fastRay with + | ValueNone -> + Log.warn "no hit" + globalBBModelSpace.Center + | ValueSome v -> + v + | ValueNone -> + if globalBBModelSpace.Contains(camPos) then + let scnd = ray.GetPointOnRay(tmax) + scnd + else + ray.GetPointOnRay(tmax) + | _ -> + globalBBModelSpace.Center + // approach: place virtual sphere with radius = triangle size at bb center + // go deeper until virtual sphere is smaller than one pixel + let localLodFocusPoint = referencePoint// arbitrary. use center for computing screen space triangle size + let lodCenterViewSpace = view.TransformPos(localLodFocusPoint) + let pointOnSphere = lodCenterViewSpace + V3d(renderPatch.triangleSize, renderPatch.triangleSize, 0.0) + let lodCenterNdc = proj.TransformPosProj(lodCenterViewSpace) + let pointOnSphereNdc = proj.TransformPosProj(pointOnSphere) + let triangleSizeInNcs = Vec.distance pointOnSphereNdc lodCenterNdc + // true = go deeper + // more restrictive condition = less LoDs + let normalizedQuality = p.factor - (-2.0) / (5.0 - (-2.0)) + // triangle size in [-1,1] space, scale to [0,1], rescale with max viewport dimension to get to pixels + // roughly. Next, go deepter if triangle is larger than largestTriangleInPixels + + let largestTriangleInPixels = 5.0 + triangleSizeInNcs * 0.5 * float p.size.NormMax > largestTriangleInPixels * normalizedQuality && legacyDecider + else + // culled + false + let createPlainSceneGraph (runtime : IRuntime) (signature : IFramebufferSignature) @@ -167,6 +291,61 @@ module Sg = PatchHierarchy.load Serialization.binarySerializer.Pickle Serialization.binarySerializer.UnPickle (OpcPaths x) ) |> Seq.toArray + + let intersect = + let rayGuidedLoD = false + if rayGuidedLoD then + let kdTrees = + patchHierarchies + |> Array.choose (fun h -> + Log.startTimed "loading lowesd kd" + let kdTree = + let level, info = + match h.tree with + | QTree.Node(p,_) -> p.level, p.info + | QTree.Leaf p -> 0, p.info + match h.kdTree_FileAbsPath info.Name -1 ViewerModality.XYZ |> KdTrees.tryFixPatchFileIfNeeded with + | None -> + Log.warn "no kd tree for level 0" + None + | Some kdPath -> + let kd = KdTrees.loadKdtree kdPath + match h.opcPaths.Patches_DirAbsPath +/ info.Name +/ info.Positions |> KdTrees.tryFixPatchFileIfNeeded with + | None -> + None + | Some objectSetPath -> + let t = DebugKdTreesX.loadTriangles' info.Local2Global objectSetPath + kd.KdIntersectionTree.ObjectSet <- t + Some (h,kd, info.GlobalBoundingBox) + Log.stop() + kdTree + ) + + let intersect (r : FastRay3d) = + let hits = + kdTrees + |> Seq.filter (fun (h,kd,bb) -> + Helper.intersectBox' bb r |> Option.isSome + ) + |> Seq.choose (fun (h,kd,bb) -> + let mutable hit = ObjectRayHit.MaxRange + let intersecBox = Helper.intersectBox' kd.KdIntersectionTree.BoundingBox3d r + if kd.KdIntersectionTree.Intersect(r, 0.0, Double.MaxValue, &hit) then + Some hit + else + None + ) + if Seq.isEmpty hits then + ValueNone + else + let h = hits |> Seq.minBy (fun h -> h.RayHit.T) + ValueSome h.RayHit.Point + + ValueSome intersect + + else + ValueNone + let kdTreesPerHierarchy = [| @@ -215,8 +394,10 @@ module Sg = | _ -> AVal.constant false :> IAdaptiveValue ] - let lodDeciderMars = lodDeciderMars scene.preTransform + //let lodDeciderMars = lodDeciderMars scene.preTransform //let lodDeciderMars = marsArea scene.preTransform + //let lodDeciderMars = reworkedLoD scene.preTransform intersect + let lodDeciderMars = cleanedOldLegacyLoD scene.preTransform let map = Map.ofList [ @@ -260,25 +441,6 @@ module Sg = Some (getVertexAttributes h.opcPaths), Aardvark.Data.PixImagePfim.Loader ) - - //let plainPatchLod = - // Sg.patchLod' - // signature - // runner - // h.opcPaths.Opc_DirAbsPath - // lodDeciderMars //scene.lodDecider - // scene.useCompressedTextures - // true - // ViewerModality.XYZ - // PatchLod.CoordinatesMapping.Local - // useAsyncLoading - // (PatchLod.toRoseTree h.tree) - // map - // (fun n s -> - // let vp = s.FootprintVP - // vp :> obj - // ) - //plainPatchLod patchLodWithTextures ) |> SgFSharp.Sg.ofArray diff --git a/src/PRo3D.Viewer/InitialViewerModel.fs b/src/PRo3D.Viewer/InitialViewerModel.fs index f940dcc1..d96e91c7 100644 --- a/src/PRo3D.Viewer/InitialViewerModel.fs +++ b/src/PRo3D.Viewer/InitialViewerModel.fs @@ -73,6 +73,9 @@ module Viewer = //let defaultDashboard = DashboardModes.provenance let defaultDockConfig = defaultDashboard.dockConfig //DockConfigs.m2020 let viewConfigModel = ViewConfigModel.initial + + let applyProvenaceIfEnabled (m : Model) = + ProvenanceApp.emptyWithModel startupArgs.enableProvenanceTracking m { scene = { @@ -172,6 +175,6 @@ module Viewer = animator = Animation.Animator.initial animatorLens provenanceModel = ProvenanceModel.invalid - } |> ProvenanceApp.emptyWithModel + } |> applyProvenaceIfEnabled diff --git a/src/PRo3D.Viewer/Program.fs b/src/PRo3D.Viewer/Program.fs index 284d9794..a868ab67 100644 --- a/src/PRo3D.Viewer/Program.fs +++ b/src/PRo3D.Viewer/Program.fs @@ -368,7 +368,13 @@ let main argv = choose [] let suaveServer = - WebPart.startServer port [ + let startServer = + if startupArgs.enableRemoteApi then + WebPart.startServer + else + WebPart.startServerLocalhost + + startServer port [ if startupArgs.disableCors then allow_cors MutableApp.toWebPart' runtime false mainApp path "/websocket" >=> handShake ws diff --git a/src/PRo3D.Viewer/ProvenanceApp.fs b/src/PRo3D.Viewer/ProvenanceApp.fs index bbdd3c0c..d2786371 100644 --- a/src/PRo3D.Viewer/ProvenanceApp.fs +++ b/src/PRo3D.Viewer/ProvenanceApp.fs @@ -90,23 +90,25 @@ module ProvenanceApp = } - let emptyWithModel (m : Model) = + let emptyWithModel (enabled : bool) (m : Model) = + if enabled then + let i = { id = ProvenanceModel.newNodeId(); model = reduceModel m |> Some } - let i = { id = ProvenanceModel.newNodeId(); model = reduceModel m |> Some } - - let pm = { - nodes = HashMap.ofList [i.id, i] - edges = HashMap.empty; lastEdge = None - automaticRecording = false - currentTrail = [] - selectedNode = None - initialNode = Some i.id - } - - - { m with - provenanceModel = pm - } + let pm = { + nodes = HashMap.ofList [i.id, i] + edges = HashMap.empty; lastEdge = None + automaticRecording = false + currentTrail = [] + selectedNode = None + initialNode = Some i.id + } + + + { m with + provenanceModel = pm + } + else + m let track (oldModel : Model) (newModel : Model) (msg : ViewerAnimationAction) : Model = if newModel.provenanceModel.automaticRecording then diff --git a/src/PRo3D.Viewer/Viewer/Viewer.fs b/src/PRo3D.Viewer/Viewer/Viewer.fs index 11fec86f..645af0a0 100644 --- a/src/PRo3D.Viewer/Viewer/Viewer.fs +++ b/src/PRo3D.Viewer/Viewer/Viewer.fs @@ -1099,7 +1099,6 @@ module ViewerApp = m.screenshotDirectory _animator m.viewerVersion - |> ProvenanceApp.emptyWithModel { initialModel with recent = m.recent} |> ViewerIO.loadRoverData @@ -2219,7 +2218,7 @@ module ViewerApp = if startEmpty |> not then PRo3D.Viewer.Viewer.initial messagingMailbox StartupArgs.initArgs renderingUrl dataSamples screenshotDirectory _animator viewerVersion - |> ProvenanceApp.emptyWithModel + |> ProvenanceApp.emptyWithModel enableProvenance |> SceneLoader.loadLastScene runtime signature |> SceneLoader.loadLogBrush |> ViewerIO.loadRoverData @@ -2235,7 +2234,7 @@ module ViewerApp = else PRo3D.Viewer.Viewer.initial messagingMailbox StartupArgs.initArgs renderingUrl dataSamples screenshotDirectory _animator viewerVersion - |> ProvenanceApp.emptyWithModel + |> ProvenanceApp.emptyWithModel enableProvenance |> ViewerIO.loadRoverData let app = {