Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds thermal simulation, electrical circuit optimizer, mathematics and some more data structures #23

Open
wants to merge 96 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
96 commits
Select commit Hold shift + click to select a range
2100c34
Add basic FEM thermal simulator
Grissess Feb 25, 2023
4ed610f
Add tests, some QoL improvements to the Thermal API
Grissess Feb 25, 2023
8867a34
Add some thermal documentation
Grissess Feb 25, 2023
a3f07b0
Address some Kotlin issues in thermal
Grissess Feb 25, 2023
38d02bd
Refactor thermal, changing some names, and find a new home for Material
Grissess Feb 26, 2023
f626c56
Fixes ghost newlines and moves thermal units to another file
empireu Mar 6, 2023
484e3ae
Renames "Thermal" to "ThermalUnits" to avoid confusions in the future
empireu Mar 6, 2023
1048d15
Renames Temperature#in_ to Temperature#to (it seems more sensible bec…
empireu Mar 6, 2023
35f7f31
Merge pull request #20 from empireu/feature/thermal
Grissess Mar 6, 2023
baf0e23
Explain the thermal ST rationale
Grissess Mar 6, 2023
565eb64
Remove the stale placeholder README
Grissess Mar 6, 2023
1c4d9da
Nip a pernicious typo in the bud
Grissess Mar 6, 2023
5a6fe5f
Add PowerSources
Grissess Mar 6, 2023
e33b718
Implement inefficiency and damping in thermal simulation
Grissess Mar 6, 2023
2254221
Add source-specific accessors
Grissess Mar 6, 2023
ee2dcdf
Merge branch 'feature/powersource' into next
Grissess Mar 6, 2023
fb97183
Unify components that can report power
Grissess Mar 7, 2023
f304be7
Scale D term by time period, fix some test warnings
Grissess Mar 7, 2023
8cd84bb
Add a scaling factor (vis. "area") for thermal connections
Grissess Mar 7, 2023
c4648d1
Merge branch 'feature/thermal' into next
Grissess Mar 7, 2023
2eeadc1
Add colorspaces, especially for thermal simulation
Grissess Mar 7, 2023
2d0b79e
Big changes in thermal
Grissess Mar 7, 2023
a83e7f1
Merge branch 'feature/thermal' into next
Grissess Mar 7, 2023
0adf20a
Add BiMaps
Grissess Mar 8, 2023
a4fa159
Allow a stricter API for non-replacement in BiMaps
Grissess Mar 9, 2023
f2c19d7
Address #20 review issues
Grissess Mar 12, 2023
6a7e7a5
Merge branch 'feature/thermal' into next
Grissess Mar 12, 2023
8ed5950
Implement Lines, a serial Resistor abstraction
Grissess Mar 12, 2023
ee10dd3
Hotfix: Reset unknowns to zero before trying to solve a broken circuit
Grissess May 25, 2023
6078f47
Hotfix: Fix Switch and its tests
Grissess May 28, 2023
46af7fd
Add test for zeroing unknowns on step failure
Grissess May 28, 2023
526bd62
Implements quantity system
empireu Sep 22, 2023
1731c61
Merge pull request #21 from empireu/next
Grissess Nov 3, 2023
50e659a
Reset data only if solve failed
Grissess Nov 3, 2023
9855587
Don't construct objects to test containment
Grissess Nov 4, 2023
b6f3171
Test the last commit
Grissess Nov 4, 2023
020c06b
Fix PowerSource
Grissess Nov 12, 2023
63e82b3
Merge branch 'main' into next
Grissess Nov 12, 2023
7fad051
Removes Temperature and replaces with Quantity
empireu Nov 24, 2023
285e28e
Removes IProcess, moves Scale to Quantity and optimizes some MultiMap…
empireu Nov 24, 2023
b75d986
Adds a couple small math functions and removes FunctionTable.kt
empireu Nov 25, 2023
c88ea87
Adds IResistor interface (useful downstream)
empireu Nov 26, 2023
72563e5
Upgrades MultiSet
empireu Nov 26, 2023
701cb1d
Adds pixar ONB
empireu Nov 26, 2023
f378a4f
Adds Dual tests
empireu Nov 29, 2023
7dcda9e
Adds more Dual functions and tests and polishes some geometry routines
empireu Nov 29, 2023
09fc60a
Polishes more geometry and adds some rudimentary geometry tests
empireu Nov 29, 2023
68b19c2
More polishing
empireu Nov 30, 2023
901080a
More tests, more polishing
empireu Nov 30, 2023
565b9c2
Haphazard Matrix4x4 test
empireu Nov 30, 2023
17896d9
Removes branchless Pose2d methods
empireu Nov 30, 2023
fe643b1
Doubles up Color
empireu Nov 30, 2023
4f6302a
Doubles up emissionColor
empireu Dec 1, 2023
0e103c8
More polishing
empireu Dec 1, 2023
692e417
Removes GenericCircuit, adds QuantityArray
empireu Dec 1, 2023
81039f4
Adds Classify API to Quantity
empireu Dec 1, 2023
95cb96a
Merge pull request #22 from empireu/feature/refactor
Grissess Dec 1, 2023
c3805a7
Add back the Grade units
Grissess Dec 1, 2023
8f81134
Fixes temperature test naming convention
empireu Dec 1, 2023
7e545e9
Adds more docs to quantity
empireu Dec 1, 2023
907c0ee
Implements some ⅅ optimizations
empireu Dec 6, 2023
eebc629
More dual optimizations and kotlin opt-in for unsafe APIs
empireu Dec 7, 2023
c12dee9
Chemistry, Quantities and You: Volume 1 - The Periodic Table of Elements
empireu Dec 8, 2023
a3babc0
More Geometry tests
empireu Dec 9, 2023
4a0b382
Adds ThermalMassDefinition
empireu Dec 9, 2023
a5906bb
Uses Classify for Material.toString
empireu Dec 9, 2023
c87c344
Implements quantity multipliers
empireu Dec 9, 2023
0a015ae
More Quantity improvements
empireu Dec 9, 2023
007d6e7
More Quantity
empireu Dec 9, 2023
983b978
Modifies intersectGrid3d to return the result without conversion
empireu Dec 10, 2023
c75368f
Modifies intersectGrid3d to just call a function
empireu Dec 10, 2023
db16978
Blunder #1
empireu Dec 11, 2023
759d7ca
Performance chapter 1: Line decomposition
empireu Dec 14, 2023
c4a2c09
Quantity, but with kotlin.reflect
empireu Dec 15, 2023
bce1ae5
Adds bimap of dimension types to Quantity
empireu Dec 15, 2023
f5f25d4
Quantity classifier clean-up
empireu Dec 15, 2023
82ab0a6
Makes DIMENSION_TYPES also record dimension types of non-auxiliary sc…
empireu Dec 15, 2023
c3e8928
More [DimensionClassifier]s
empireu Dec 15, 2023
7e78fd5
Removes Space and tweaks ScaleClassifier
empireu Dec 15, 2023
a53721c
Bump down to Java 8 to test builds
Grissess Dec 18, 2023
ad78115
Merge branch 'next' of https://github.com/age-series/libage into next
Grissess Dec 18, 2023
9cc61e7
Fix a typo in the foregoing
Grissess Dec 18, 2023
baa55be
Adds Plane3d
empireu Dec 25, 2023
a18f793
Adds OBB
empireu Dec 25, 2023
7aa6f0a
FIXES IDE PERFORMANCE BY CUTTING Geometry.kt INTO PIECES and adds ray…
empireu Dec 26, 2023
51c488a
Frak you
empireu Dec 26, 2023
d3331c5
Various things
empireu Dec 27, 2023
8e8aaa2
Removes misleading BoundingBox3d constructor
empireu Dec 27, 2023
a922918
Adds Locator API
empireu Dec 29, 2023
eba674d
Adds some utility functions to locator API
empireu Dec 29, 2023
f8b9fda
Adds locator equality tests
empireu Dec 29, 2023
3ce43c5
Fixes some AABB-ray issues and adds Line3d and Cylinder3d
empireu Jan 2, 2024
7315d84
Stuff
empireu Jan 5, 2024
cb1f7db
Adds AABB tree
empireu Jan 5, 2024
7269b13
Adds AABB from Cylinder
empireu Jan 5, 2024
06f375f
Adds Ray-Cylinder and a better cheap Rotation3d normalize
empireu Jan 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ dependencies {
testImplementation("org.assertj", "assertj-core", "3.22.0")
testImplementation("org.junit.jupiter", "junit-jupiter-api", "5.8.2")
testRuntimeOnly("org.junit.jupiter", "junit-jupiter-engine", "5.8.2")
implementation(kotlin("reflect"))
}

tasks {
Expand Down Expand Up @@ -94,18 +95,25 @@ tasks {
// By default, build everything, put it somewhere convenient, and run the tests.
defaultTasks = mutableListOf("build", "test")

val compileArgs = listOf(
"-Xopt-in=kotlin.RequiresOptIn"
)

val compileKotlin: KotlinCompile by tasks
compileKotlin.kotlinOptions {
jvmTarget = "17"
jvmTarget = "1.8"
freeCompilerArgs += compileArgs
}

val compileTestKotlin: KotlinCompile by tasks
compileTestKotlin.kotlinOptions {
jvmTarget = "17"
jvmTarget = "1.8"
freeCompilerArgs += compileArgs
}

java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(17))
languageVersion.set(JavaLanguageVersion.of(8))
}
}

Expand Down
359 changes: 359 additions & 0 deletions src/main/kotlin/org/ageseries/libage/data/BVH.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,359 @@
@file:Suppress("NOTHING_TO_INLINE")

package org.ageseries.libage.data

import org.ageseries.libage.mathematics.geometry.BoundingBox3d
import java.util.*
import kotlin.collections.ArrayDeque
import kotlin.math.max

class BoundingBoxTree3d<T> {
private var rootInternal: NodeInternal<T>? = null

/**
* Gets the root of the tree. Null if no objects are present.
* */
val root : Node<T>? get() = rootInternal

private val leavesInternal = HashMap<T, NodeInternal<T>>()

/**
* Gets a map of [T] to the leaf node assigned to it.
* */
val leaves : Map<T, Node<T>> get() = leavesInternal

private inline fun cost(leaf: BoundingBox3d, parent: BoundingBox3d) = (leaf unionWith parent).surface

private inline fun inheritedCost(leaf: BoundingBox3d, sibling: BoundingBox3d) = (leaf unionWith sibling).surface - sibling.surface

private fun getSibling(leafBoundingBox: BoundingBox3d) : NodeInternal<T> {
val leafSurface = leafBoundingBox.surface
var bestCost = Double.MAX_VALUE
var bestNode = rootInternal!!

data class Item<T>(val node: NodeInternal<T>, val cost: Double)

val queue = PriorityQueue<Item<T>> { a, b ->
a.cost.compareTo(b.cost)
}

queue.add(Item(rootInternal!!, inheritedCost(leafBoundingBox, rootInternal!!.box)))

while (queue.isNotEmpty()) {
val (node, nodeCost) = queue.remove()

val cost = nodeCost + cost(leafBoundingBox, node.box)

if(cost < bestCost) {
bestCost = cost
bestNode = node
}

val inheritedCost = nodeCost + inheritedCost(leafBoundingBox, node.box)

if(leafSurface + inheritedCost < bestCost) {
val child1 = node.child1

if(child1 != null) {
val child2 = checkNotNull(node.child2)
queue.add(Item(child1, inheritedCost))
queue.add(Item(child2, inheritedCost))
}
else {
check(node.child2 == null)
}
}
}

return bestNode
}

private fun balance(a: NodeInternal<T>) : NodeInternal<T> {
if(a.isLeaf || a.height < 2) {
return a
}

val b = checkNotNull(a.child1)
val c = checkNotNull(a.child2)
val balance = c.height - b.height

return when {
balance > 1 -> {
val f = checkNotNull(c.child1)
val g = checkNotNull(c.child2)

c.child1 = a
c.parent = a.parent
a.parent = c

if (c.parent == null) {
rootInternal = c
}
else {
val cParent = c.parent!!

if(cParent.child1 == a) {
cParent.child1 = c
} else {
check(cParent.child2 == a)
cParent.child2 = c
}
}

if(f.height > g.height) {
c.child2 = f
a.child2 = g
g.parent = a
a.box = b.box unionWith g.box
c.box = a.box unionWith f.box
a.height = max(b.height, g.height) + 1
c.height = max(a.height, f.height) + 1
}
else {
c.child2 = g
a.child2 = f
f.parent = a
a.box = b.box unionWith f.box
c.box = a.box unionWith g.box
a.height = max(b.height, f.height) + 1
c.height = max(a.height, g.height) + 1
}

c
}
balance < -1 -> {
val d = checkNotNull(b.child1)
val e = checkNotNull(b.child2)

b.child1 = a
b.parent = a.parent
a.parent = b

if (b.parent == null) {
rootInternal = b
}
else {
val bParent = b.parent!!
if(bParent.child1 == a) {
bParent.child1 = b
} else {
check(bParent.child2 == a)
bParent.child2 = b
}
}

if(d.height > e.height) {
b.child2 = d
a.child1 = e
e.parent = a
a.box = c.box unionWith e.box
b.box = a.box unionWith d.box
a.height = max(c.height, e.height) + 1
b.height = max(a.height, d.height) + 1
}
else {
b.child2 = e
a.child1 = d
d.parent = a
a.box = c.box unionWith d.box
b.box = a.box unionWith e.box
a.height = max(c.height, d.height) + 1
b.height = max(a.height, e.height) + 1
}

b
}
else -> {
a
}
}
}

private fun fixUp(node: NodeInternal<T>?) {
var currentNode = node

while (currentNode != null) {
currentNode = balance(currentNode)

val child1 = checkNotNull(currentNode.child1)
val child2 = checkNotNull(currentNode.child2)

currentNode.height = max(child1.height, child2.height) + 1
currentNode.box = child1.box unionWith child2.box

currentNode = currentNode.parent
}
}

/**
* Gets the leaf node of [key] or null, if [key] is not present.
* */
operator fun get(key: T): Node<T>? = leavesInternal[key]

/**
* Checks if [key] is inserted.
* */
fun contains(key: T) = leavesInternal.containsKey(key)

/**
* Inserts [data] with the specified [boundingBox]. Throws if [data] is already present.
* */
fun insert(data: T, boundingBox: BoundingBox3d) : Node<T> {
val leaf = NodeInternal<T>()
leaf.data = data
leaf.box = boundingBox

require(leavesInternal.put(data, leaf) == null) {
"Duplicate insert $data"
}

if(rootInternal == null) {
rootInternal = leaf
return leaf
}

val sibling = getSibling(boundingBox)

val oldParent = sibling.parent
val newParent = NodeInternal<T>()
newParent.parent = oldParent
newParent.box = boundingBox unionWith sibling.box
newParent.height = sibling.height + 1

if (oldParent == null) {
rootInternal = newParent
}
else {
if(oldParent.child1 == sibling) {
oldParent.child1 = newParent
} else {
oldParent.child2 = newParent
}
}

newParent.child1 = sibling
newParent.child2 = leaf
sibling.parent = newParent
leaf.parent = newParent

fixUp(newParent)

return leaf
}

/**
* Removes [data]. Throws an exception if [data] is not present.
* */
fun remove(data: T) : Node<T> {
val leaf = requireNotNull(leavesInternal.remove(data)) {
"Did not have $data"
}

if(leaf == rootInternal) {
rootInternal = null
return leaf
}

val parent = checkNotNull(leaf.parent)

val sibling = checkNotNull(
if(parent.child1 == leaf) {
parent.child2
}
else {
check(parent.child2 == leaf)
parent.child1
}
)

val grandparent = parent.parent

if (grandparent == null) {
rootInternal = sibling
sibling.parent = null
}
else {
if(grandparent.child1 == parent) {
grandparent.child1 = sibling
} else {
check(grandparent.child2 == parent)
grandparent.child2 = sibling
}

sibling.parent = grandparent

fixUp(grandparent)
}

return leaf
}

/**
* Queries for leaves that pass the [test] and whose ancestors pass the [test].
* Results are passed to [consumer]. If [consumer] returns false, the search ends.
* */
inline fun queryIntersecting(test: (Node<T>) -> Boolean, consumer: (Node<T>) -> Boolean) {
if(root == null) {
return
}

val stack = ArrayDeque<Node<T>>()
stack.addLast(root!!)

while (stack.isNotEmpty()) {
val node = stack.removeLast()

if(test(node)) {
if(node.isLeaf) {
if(!consumer(node)) {
return
}
}
else {
stack.addLast(node.child1!!)
stack.addLast(node.child2!!)
}
}
}
}

interface Node<T> {
/**
* Gets the data stored in the node. Not null if and only if [isLeaf] is true.
* */
val data: T?
/**
* Gets the bounding box of the node. For [isLeaf] nodes, it will be the bounding box passed to [insert].
* */
val box: BoundingBox3d
/**
* Gets the parent of the node. Not null if and only if this node is not the root.
* */
val parent: Node<T>?
/**
* Gets the first child. Not null if and only if this node is not [isLeaf].
* */
val child1: Node<T>?
/**
* Gets the second child. Not null if and only if this node is not [isLeaf].
* */
val child2: Node<T>?
/**
* Gets the height at this node. 0 if [isLeaf].
* */
val height: Int
/**
* If true, this node is a leaf node. Otherwise, this node is an internal node and [child1], [child2] are not null.
* */
val isLeaf get() = child1 == null
}

private class NodeInternal<T> : Node<T> {
override var data: T? = null
override var box = BoundingBox3d.zero
override var parent: NodeInternal<T>? = null
override var child1: NodeInternal<T>? = null
override var child2: NodeInternal<T>? = null
override var height = 0
}
}
Loading
Loading