From ae7b07fd2a47de1fb2c8e0a3a8e294a71c26ff90 Mon Sep 17 00:00:00 2001 From: li3zhen1 Date: Mon, 9 Oct 2023 18:45:07 -0400 Subject: [PATCH] bugfix: quad delegate --- .../ForceSimulation/forces/CollideForce.swift | 2 + .../forces/ManyBodyForce.swift | 4 +- Sources/QuadTree/QuadTree2.swift | 25 +++++---- Tests/QuadTreeTests/AddTests.swift | 54 ++++++++++++++++--- Tests/QuadTreeTests/QuadDelegateTests.swift | 9 ++++ Tests/QuadTreeTests/Utils/Nodes.swift | 4 +- 6 files changed, 73 insertions(+), 25 deletions(-) create mode 100644 Tests/QuadTreeTests/QuadDelegateTests.swift diff --git a/Sources/ForceSimulation/forces/CollideForce.swift b/Sources/ForceSimulation/forces/CollideForce.swift index abce1f1..539b126 100644 --- a/Sources/ForceSimulation/forces/CollideForce.swift +++ b/Sources/ForceSimulation/forces/CollideForce.swift @@ -12,6 +12,8 @@ enum CollideForceError: Error { } + + public class CollideForce where N : Identifiable { let radius: CollideRadius diff --git a/Sources/ForceSimulation/forces/ManyBodyForce.swift b/Sources/ForceSimulation/forces/ManyBodyForce.swift index 7206957..c9dc008 100644 --- a/Sources/ForceSimulation/forces/ManyBodyForce.swift +++ b/Sources/ForceSimulation/forces/ManyBodyForce.swift @@ -57,7 +57,7 @@ final class MassQuadTreeDelegate: QuadDelegate where N : Identifiable { } - func createForExpanded(towards _: Quadrant, from _: Quad, to _: Quad) -> Self { + func copy() -> Self { return Self( initialAccumulatedProperty: self.accumulatedProperty, initialAccumulatedCount: self.accumulatedCount, @@ -66,7 +66,7 @@ final class MassQuadTreeDelegate: QuadDelegate where N : Identifiable { ) } - func stem() -> Self { + func createNew() -> Self { return Self(massProvider: self.massProvider) } diff --git a/Sources/QuadTree/QuadTree2.swift b/Sources/QuadTree/QuadTree2.swift index 205ed95..1b70741 100644 --- a/Sources/QuadTree/QuadTree2.swift +++ b/Sources/QuadTree/QuadTree2.swift @@ -48,18 +48,15 @@ public class QuadTreeNode2 where N: Identifiable, QD: QuadDelegate, QD.No ) { self.quad = quad self.clusterDistance = clusterDistance - self.quadDelegate = rootQuadDelegate.stem() + self.quadDelegate = rootQuadDelegate.createNew() } public func add(_ node: N, at point: Vector2f) { cover(point) - // accumulatedCount += 1 - - quadDelegate.didAddNode(node, at: point) - - // accumulatedProperty += QD.getPropertyFor(node) - // weightedAccumulatedNodePositions += node.quadDelegate * point + defer { + quadDelegate.didAddNode(node, at: point) + } guard let children = self.children else { if nodes.isEmpty { @@ -79,6 +76,8 @@ public class QuadTreeNode2 where N: Identifiable, QD: QuadDelegate, QD.No if !nodes.isEmpty { let direction = quad.quadrantOf(nodes.first!.value) divided[at: direction].nodes = self.nodes + divided[at: direction].quadDelegate = self.quadDelegate + self.quadDelegate = self.quadDelegate.copy() } self.nodes = [:] @@ -167,15 +166,15 @@ public class QuadTreeNode2 where N: Identifiable, QD: QuadDelegate, QD.No self.quad = newRootQuad self.children = divided self.nodes = [:] - self.quadDelegate = quadDelegate.createForExpanded(towards: quadrant, from: copiedCurrentNode.quad, to: newRootQuad) + self.quadDelegate = quadDelegate.copy() } private static func divide(quad: Quad, clusterDistance: Float, rootQuadDelegate: QD) -> Children { let divided = quad.divide() let northWest = QuadTreeNode2(quad: divided.northWest, clusterDistance: clusterDistance, rootQuadDelegate: rootQuadDelegate) - let northEast = QuadTreeNode2(quad: divided.northEast, clusterDistance: clusterDistance,rootQuadDelegate:rootQuadDelegate) - let southWest = QuadTreeNode2(quad: divided.southWest, clusterDistance: clusterDistance,rootQuadDelegate:rootQuadDelegate) - let southEast = QuadTreeNode2(quad: divided.southEast, clusterDistance: clusterDistance,rootQuadDelegate:rootQuadDelegate) + let northEast = QuadTreeNode2(quad: divided.northEast, clusterDistance: clusterDistance, rootQuadDelegate: rootQuadDelegate) + let southWest = QuadTreeNode2(quad: divided.southWest, clusterDistance: clusterDistance, rootQuadDelegate: rootQuadDelegate) + let southEast = QuadTreeNode2(quad: divided.southEast, clusterDistance: clusterDistance, rootQuadDelegate: rootQuadDelegate) return Children(northWest, northEast, southWest, southEast) } @@ -355,8 +354,8 @@ public protocol QuadDelegate { mutating func didAddNode(_ node: Node, at position: Vector2f) mutating func didRemoveNode(_ node: Node, at position: Vector2f) - func createForExpanded(towards: Quadrant, from oldQuad: Quad, to newQuad: Quad) -> Self - func stem() -> Self + func copy() -> Self + func createNew() -> Self } diff --git a/Tests/QuadTreeTests/AddTests.swift b/Tests/QuadTreeTests/AddTests.swift index 158e8a2..9961c64 100644 --- a/Tests/QuadTreeTests/AddTests.swift +++ b/Tests/QuadTreeTests/AddTests.swift @@ -3,6 +3,36 @@ import XCTest +final class EmptyQuadDelegate: QuadDelegate { + + var count: Int + + init(count: Int = 0) { + self.count = count + } + + func didAddNode(_ node: IdNode, at position: Vector2f) { + count += 1 + } + + func didRemoveNode(_ node: IdNode, at position: Vector2f) { + count -= 1 + } + + func copy() -> Self { + return Self(count: self.count) + } + + func createNew() -> Self { + return Self(count: 0) + } + + typealias Node = IdNode + + typealias Property = () + + +} @@ -21,21 +51,25 @@ final class AddTests: XCTestCase { }); */ func testCreatePoint() { - let q = QuadTree.create(startingWith: IdNode.new(), at: Vector2f(x: 0, y:0)) + let q = QuadTree2.create(startingWith: IdNode.new(), at: Vector2f(x: 0, y:0)) { EmptyQuadDelegate() } assert(q.jsStyleDescription ~= "{data: [0.0, 0.0]}") + assert(q.root.quadDelegate.count == 1) q.add(IdNode.new(), at: Vector2f(x: 0.9, y:0.9)) assert(q.jsStyleDescription ~= "[{data: [0.0, 0.0]},,, {data: [0.9, 0.9]}]") + assert(q.root.quadDelegate.count == 2) q.add(IdNode.new(), at: Vector2f(x: 0.9, y:0.0)) assert(q.jsStyleDescription ~= "[{data: [0.0, 0.0]}, {data: [0.9, 0.0]},, {data: [0.9, 0.9]}]") + assert(q.root.quadDelegate.count == 3) q.add(IdNode.new(), at: Vector2f(x: 0.0, y:0.9)) assert(q.jsStyleDescription ~= "[{data: [0.0, 0.0]}, {data: [0.9, 0.0]}, {data: [0.0, 0.9]}, {data: [0.9, 0.9]}]") + assert(q.root.quadDelegate.count == 4) q.add(IdNode.new(), at: Vector2f(x: 0.4, y:0.4)) assert(q.jsStyleDescription ~= "[[{data: [0.0, 0.0]},,, {data: [0.4, 0.4]}], {data: [0.9, 0.0]}, {data: [0.0, 0.9]}, {data: [0.9, 0.9]}]") - + assert(q.root.quadDelegate.count == 5) } @@ -50,19 +84,23 @@ final class AddTests: XCTestCase { */ func testCreatePointOnPerimeter() { - let q = QuadTree(quad: Quad(x0: 0.0, x1: 1.0, y0: 0.0, y1: 1.0)) + let q = QuadTree2(quad: Quad(x0: 0.0, x1: 1.0, y0: 0.0, y1: 1.0)) { EmptyQuadDelegate() } q.add(IdNode.new(), at: Vector2f(x: 0, y:0)) assert(q.jsStyleDescription ~= "{data: [0.0, 0.0]}") + assert(q.root.quadDelegate.count == 1) q.add(IdNode.new(), at: Vector2f(x: 1, y:1)) assert(q.jsStyleDescription ~= "[{data: [0.0, 0.0]},,, {data: [1.0, 1.0]}]") + assert(q.root.quadDelegate.count == 2) q.add(IdNode.new(), at: Vector2f(x: 1, y:0)) assert(q.jsStyleDescription ~= "[{data: [0.0, 0.0]}, {data: [1.0, 0.0]},, {data: [1.0, 1.0]}]") + assert(q.root.quadDelegate.count == 3) q.add(IdNode.new(), at: Vector2f(x: 0, y:1)) assert(q.jsStyleDescription ~= "[{data: [0.0, 0.0]}, {data: [1.0, 0.0]}, {data: [0.0, 1.0]}, {data: [1.0, 1.0]}]") + assert(q.root.quadDelegate.count == 4) } @@ -71,7 +109,7 @@ final class AddTests: XCTestCase { // assert.deepStrictEqual(q.add([1, -1]).extent(), [[0, -4], [8, 4]]); // }); func testCreatePointOnTop() { - let q = QuadTree(quad: Quad(x0: 0.0, x1: 2.0, y0: 0.0, y1: 2.0)) + let q = QuadTree2(quad: Quad(x0: 0.0, x1: 2.0, y0: 0.0, y1: 2.0)) { EmptyQuadDelegate() } q.add(IdNode.new(), at: Vector2f(x: 1, y:-1)) assert(q.root.quad ~= Quad(x0: 0, x1: 4, y0: -2, y1: 2)) } @@ -83,7 +121,7 @@ final class AddTests: XCTestCase { // assert.deepStrictEqual(q.add([1, 3]).extent(), [[0, 0], [4, 4]]); // }); func testCreatePointOnBottom() { - let q = QuadTree(quad: Quad(x0: 0.0, x1: 2.0, y0: 0.0, y1: 2.0)) + let q = QuadTree2(quad: Quad(x0: 0.0, x1: 2.0, y0: 0.0, y1: 2.0)) { EmptyQuadDelegate() } q.add(IdNode.new(), at: Vector2f(x: 1, y:3)) assert(q.root.quad ~= Quad(x0: 0, x1: 4, y0: 0, y1: 4)) } @@ -93,7 +131,7 @@ final class AddTests: XCTestCase { // assert.deepStrictEqual(q.add([-1, 1]).extent(), [[-4, 0], [4, 8]]); // }); func testCreatePointOnLeft() { - let q = QuadTree(quad: Quad(x0: 0.0, x1: 2.0, y0: 0.0, y1: 2.0)) + let q = QuadTree2(quad: Quad(x0: 0.0, x1: 2.0, y0: 0.0, y1: 2.0)) { EmptyQuadDelegate() } q.add(IdNode.new(), at: Vector2f(x: -1, y:1)) assert(q.root.quad ~= Quad(x0: -2, x1: 2, y0: 0, y1: 4)) } @@ -106,7 +144,7 @@ final class AddTests: XCTestCase { // assert.deepStrictEqual(q.add([0, 1]).root(), [{data: [0, 0]}, {data: [1, 0]}, {data: [0, 1], next: {data: [0, 1]}},, ]); // }); func testCreateCoincidentPoints() { - let q = QuadTree(quad: Quad(x0: 0.0, x1: 1.0, y0: 0.0, y1: 1.0)) + let q = QuadTree2(quad: Quad(x0: 0.0, x1: 1.0, y0: 0.0, y1: 1.0)) {EmptyQuadDelegate()} q.add(IdNode.new(), at: Vector2f(x: 0, y:0)) assert(q.jsStyleDescription ~= "{data: [0.0, 0.0]}") @@ -126,7 +164,7 @@ final class AddTests: XCTestCase { // assert.deepStrictEqual(q.root(), {data: [1, 2]}); // }); func testCreateFirstPoint() { - let q = QuadTree.create(startingWith: .new(), at: .init(1, 2)) + let q = QuadTree2.create(startingWith: .new(), at: .init(1, 2)) { EmptyQuadDelegate() } assert(q.quad ~= Quad(x0: 1, x1: 2, y0: 2, y1: 3)) assert(q.jsStyleDescription ~= "{data: [1.0, 2.0]}") diff --git a/Tests/QuadTreeTests/QuadDelegateTests.swift b/Tests/QuadTreeTests/QuadDelegateTests.swift new file mode 100644 index 0000000..8ae9532 --- /dev/null +++ b/Tests/QuadTreeTests/QuadDelegateTests.swift @@ -0,0 +1,9 @@ +// +// QuadDelegateTests.swift +// +// +// Created by li3zhen1 on 10/9/23. +// + +import Foundation + diff --git a/Tests/QuadTreeTests/Utils/Nodes.swift b/Tests/QuadTreeTests/Utils/Nodes.swift index 7bfec7e..4f80e1f 100644 --- a/Tests/QuadTreeTests/Utils/Nodes.swift +++ b/Tests/QuadTreeTests/Utils/Nodes.swift @@ -48,7 +48,7 @@ struct NamedNode: Identifiable, HasMassLikeProperty { -extension QuadTreeNode { +extension QuadTreeNode2 { var jsStyleDescription: String { if let children { return "[\(children.northWest.jsStyleDescription), \(children.northEast.jsStyleDescription), \(children.southWest.jsStyleDescription), \(children.southEast.jsStyleDescription)]" @@ -82,6 +82,6 @@ extension QuadTreeNode { } } -extension QuadTree { +extension QuadTree2 { var jsStyleDescription: String { return self.root.jsStyleDescription } }