Compare commits
No commits in common. "node" and "main" have entirely different histories.
148
.clang-format
Normal file
148
.clang-format
Normal file
@ -0,0 +1,148 @@
|
||||
Language: Cpp
|
||||
AccessModifierOffset: -4
|
||||
AlignAfterOpenBracket: Align
|
||||
AlignArrayOfStructures: Left
|
||||
AlignConsecutiveMacros: None
|
||||
AlignConsecutiveAssignments: None
|
||||
AlignConsecutiveBitFields: None
|
||||
AlignConsecutiveDeclarations: None
|
||||
AlignEscapedNewlines: Left
|
||||
AlignOperands: Align
|
||||
AlignTrailingComments: true
|
||||
AllowAllArgumentsOnNextLine: false
|
||||
AllowAllParametersOfDeclarationOnNextLine: false
|
||||
AllowShortEnumsOnASingleLine: false
|
||||
AllowShortBlocksOnASingleLine: Empty
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: Empty
|
||||
AllowShortLambdasOnASingleLine: All
|
||||
AllowShortIfStatementsOnASingleLine: Never
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AlwaysBreakAfterDefinitionReturnType: None
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: false
|
||||
AlwaysBreakTemplateDeclarations: Yes
|
||||
AttributeMacros: []
|
||||
BinPackArguments: false
|
||||
BinPackParameters: false
|
||||
BraceWrapping:
|
||||
AfterCaseLabel: false
|
||||
AfterClass: false
|
||||
AfterControlStatement: Never
|
||||
AfterEnum: false
|
||||
AfterFunction: false
|
||||
AfterNamespace: false
|
||||
AfterObjCDeclaration: false
|
||||
AfterStruct: false
|
||||
AfterUnion: false
|
||||
AfterExternBlock: false
|
||||
BeforeCatch: false
|
||||
BeforeElse: false
|
||||
BeforeLambdaBody: false
|
||||
BeforeWhile: false
|
||||
IndentBraces: false
|
||||
SplitEmptyFunction: false
|
||||
SplitEmptyRecord: false
|
||||
SplitEmptyNamespace: false
|
||||
BreakBeforeBinaryOperators: All
|
||||
BreakBeforeConceptDeclarations: true
|
||||
BreakBeforeBraces: Attach
|
||||
BreakBeforeInheritanceComma: false
|
||||
BreakInheritanceList: BeforeComma
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializersBeforeComma: true
|
||||
BreakConstructorInitializers: BeforeComma
|
||||
BreakStringLiterals: true
|
||||
ColumnLimit: 99
|
||||
QualifierAlignment: Left
|
||||
CompactNamespaces: false
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
ContinuationIndentWidth: 4
|
||||
Cpp11BracedListStyle: true
|
||||
DeriveLineEnding: true
|
||||
DerivePointerAlignment: false
|
||||
DisableFormat: false
|
||||
EmptyLineAfterAccessModifier: Never
|
||||
EmptyLineBeforeAccessModifier: Always
|
||||
ExperimentalAutoDetectBinPacking: false
|
||||
PackConstructorInitializers: NextLine
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: false
|
||||
AllowAllConstructorInitializersOnNextLine: false
|
||||
FixNamespaceComments: true
|
||||
ForEachMacros: []
|
||||
IfMacros: []
|
||||
IncludeBlocks: Merge
|
||||
IndentAccessModifiers: false
|
||||
IndentCaseLabels: false
|
||||
IndentCaseBlocks: false
|
||||
IndentGotoLabels: false
|
||||
IndentPPDirectives: None
|
||||
IndentExternBlock: AfterExternBlock
|
||||
IndentRequires: false
|
||||
IndentWidth: 4
|
||||
IndentWrappedFunctionNames: false
|
||||
InsertTrailingCommas: Wrapped
|
||||
InsertBraces: true
|
||||
InsertNewlineAtEOF: true
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
LambdaBodyIndentation: Signature
|
||||
MacroBlockBegin: ''
|
||||
MacroBlockEnd: ''
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: None
|
||||
PenaltyBreakAssignment: 2
|
||||
PenaltyBreakBeforeFirstCallParameter: 1
|
||||
PenaltyBreakComment: 300
|
||||
PenaltyBreakFirstLessLess: 120
|
||||
PenaltyBreakOpenParenthesis: 0
|
||||
PenaltyBreakString: 1000
|
||||
PenaltyBreakTemplateDeclaration: 10
|
||||
PenaltyExcessCharacter: 1000000
|
||||
PenaltyReturnTypeOnItsOwnLine: 200
|
||||
PenaltyIndentedWhitespace: 0
|
||||
PointerAlignment: Left
|
||||
PPIndentWidth: -1
|
||||
ReferenceAlignment: Pointer
|
||||
ReflowComments: true
|
||||
RemoveBracesLLVM: false
|
||||
SeparateDefinitionBlocks: Leave
|
||||
ShortNamespaceLines: 1
|
||||
SortIncludes: CaseSensitive
|
||||
SortJavaStaticImport: Before
|
||||
SortUsingDeclarations: true
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterLogicalNot: false
|
||||
SpaceAfterTemplateKeyword: false
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCaseColon: false
|
||||
SpaceBeforeCpp11BracedList: false
|
||||
SpaceBeforeCtorInitializerColon: false
|
||||
SpaceBeforeInheritanceColon: true
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpaceBeforeParensOptions:
|
||||
AfterControlStatements: true
|
||||
AfterFunctionDefinitionName: false
|
||||
AfterFunctionDeclarationName: false
|
||||
AfterOverloadedOperator: false
|
||||
BeforeNonEmptyParentheses: false
|
||||
SpaceAroundPointerQualifiers: Default
|
||||
SpaceBeforeRangeBasedForLoopColon: true
|
||||
SpaceInEmptyBlock: false
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 2
|
||||
SpacesInAngles: Never
|
||||
SpacesInConditionalStatement: false
|
||||
SpacesInContainerLiterals: false
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInLineCommentPrefix:
|
||||
Minimum: 1
|
||||
Maximum: -1
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
SpaceBeforeSquareBrackets: false
|
||||
BitFieldColonSpacing: Both
|
||||
Standard: Cpp11
|
||||
TabWidth: 4
|
||||
UseCRLF: false
|
||||
UseTab: Always
|
||||
|
18
.gitignore
vendored
18
.gitignore
vendored
@ -1,10 +1,14 @@
|
||||
# Editor
|
||||
# Executables
|
||||
*.exe
|
||||
*.out
|
||||
*.run
|
||||
|
||||
# Editors
|
||||
*.swp
|
||||
.vscode/
|
||||
|
||||
# node.js & npm
|
||||
node_modules
|
||||
|
||||
# build files
|
||||
dist/
|
||||
.parcel-cache/
|
||||
# Build
|
||||
.xmake/
|
||||
build/
|
||||
*.ilk
|
||||
*.pdb
|
BIN
assets/basic.png
Normal file
BIN
assets/basic.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.0 KiB |
@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="a" data-name="图层 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
||||
<rect x="61.699933" y="30" width="25" height="40" transform="translate(124.199933 -24.199933) rotate(90)" style="fill: #bdbdbd; stroke: #757575; stroke-linejoin: round; stroke-width: 3px;"/>
|
||||
<circle cx="50" cy="50" r="25" style="fill: #29b6f6; stroke: #757575; stroke-miterlimit: 10; stroke-width: 3px;"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 447 B |
@ -1,13 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>openarras</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
</head>
|
||||
<body>
|
||||
<canvas width="1000" height="700" id="board"></canvas>
|
||||
<p id="raw-info"></p>
|
||||
<script src="./index.js" type="module"></script>
|
||||
</body>
|
||||
</html>
|
@ -1,30 +0,0 @@
|
||||
import { io } from "socket.io-client";
|
||||
|
||||
const socket = io();
|
||||
|
||||
function loadImg(url) {
|
||||
const img_url = url;
|
||||
const img = new Image();
|
||||
img.src = img_url;
|
||||
return new Promise((resolve, reject) => {
|
||||
img.onload = () => { resolve(img); };
|
||||
img.onerror = (err) => { reject(err); };
|
||||
});
|
||||
}
|
||||
|
||||
const imgs = {};
|
||||
|
||||
async function loadAssets() {
|
||||
imgs.basic = await loadImg(new URL('assets/basic.svg', import.meta.url));
|
||||
}
|
||||
|
||||
await loadAssets();
|
||||
|
||||
const cvs = document.querySelector("#board");
|
||||
const ctx = cvs.getContext("2d");
|
||||
|
||||
socket.on("update",(tank_info) => {
|
||||
document.querySelector("#raw-info").innerHTML = JSON.stringify(tank_info);
|
||||
ctx.clearRect(0, 0, cvs.width, cvs.height);
|
||||
ctx.drawImage(imgs.basic, tank_info.position.x, tank_info.position.y, 100, 100);
|
||||
});
|
137
include/2d.h
Normal file
137
include/2d.h
Normal file
@ -0,0 +1,137 @@
|
||||
#ifndef header_2d
|
||||
#define header_2d
|
||||
|
||||
#include <array>
|
||||
#include <complex>
|
||||
#include <initializer_list>
|
||||
|
||||
namespace arras {
|
||||
|
||||
using Float = float;
|
||||
using Vec2 = std::complex<Float>;
|
||||
|
||||
const Float eps = 1e-6;
|
||||
|
||||
inline Float cross(const Vec2& a, const Vec2& b) {
|
||||
return a.real() * b.imag() - a.imag() * b.real();
|
||||
}
|
||||
|
||||
inline Float dot(const Vec2& a, const Vec2& b) {
|
||||
return a.real() * b.real() + a.imag() * b.imag();
|
||||
}
|
||||
|
||||
inline Float asAngle(const Vec2& d) {
|
||||
return std::atan2(d.imag(), d.real());
|
||||
}
|
||||
|
||||
struct Circle {
|
||||
Vec2 o;
|
||||
Float r;
|
||||
|
||||
Circle() = delete;
|
||||
Circle(const Vec2& o, Float r): o(o), r(r) {}
|
||||
|
||||
bool contain(const Vec2& a) const {
|
||||
return abs(o - a) <= r;
|
||||
}
|
||||
};
|
||||
|
||||
template<int n>
|
||||
struct Polygon {
|
||||
static_assert(n >= 3, "Polygon must have at least 3 edges");
|
||||
std::array<Vec2, n> vertex;
|
||||
|
||||
Polygon() = delete;
|
||||
Polygon(const std::array<Vec2, n>& v): vertex(v) {}
|
||||
Polygon(std::initializer_list<Vec2>&& v) {
|
||||
int i = 0;
|
||||
for (auto&& x : v) {
|
||||
if (i >= n) {
|
||||
break;
|
||||
}
|
||||
vertex[i] = x;
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
Vec2& operator[](int i) {
|
||||
return vertex[i];
|
||||
}
|
||||
|
||||
Vec2 operator[](int i) const {
|
||||
return vertex[i];
|
||||
}
|
||||
|
||||
bool contain(const Vec2& a) const {
|
||||
Float s = 0;
|
||||
for (int i = 0; i < n; i++) {
|
||||
Vec2 u = vertex[i] - a, w = vertex[(i + 1) % n] - a;
|
||||
s += atan2(cross(u, w), dot(u, w));
|
||||
}
|
||||
return abs(s) > eps;
|
||||
}
|
||||
};
|
||||
|
||||
inline bool intersect(const Circle& a, const Circle& b) {
|
||||
return abs(a.o - b.o) <= a.r + b.r;
|
||||
}
|
||||
|
||||
template<int n, int m>
|
||||
inline bool intersect(const Polygon<n>& a, const Polygon<m>& b) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (b.contain(a[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < m; i++) {
|
||||
if (a.contain(b[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
for (int j = 0; j < m; j++) {
|
||||
if (cross(b[j] - a[i], a[(i + 1) % n] - a[i])
|
||||
* cross(b[(j + 1) % m] - a[i], a[(i + 1) % n] - a[i])
|
||||
<= 0
|
||||
&& cross(a[i] - b[j], b[(j + 1) % m] - b[j])
|
||||
* cross(a[(i + 1) % n] - b[j], b[(j + 1) % m] - b[j])
|
||||
<= 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template<int n>
|
||||
inline bool intersect(const Polygon<n>& a, const Circle& b) {
|
||||
if (a.contain(b.o)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (b.contain(a[i])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
Vec2 u = b.o - a[i], v = a[(i + 1) % n] - a[i];
|
||||
Float c = dot(u, v) / norm(v);
|
||||
if (0 <= c && c <= 1 && abs(u - c * v) <= b.r) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
template<int n>
|
||||
inline bool intersect(const Circle& b, const Polygon<n>& a) {
|
||||
return intersect(a, b);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
98
include/render.h
Normal file
98
include/render.h
Normal file
@ -0,0 +1,98 @@
|
||||
#ifndef HEADER_ARRAS_RENDER_H
|
||||
#define HEADER_ARRAS_RENDER_H
|
||||
|
||||
extern "C" {
|
||||
#include <SDL2/SDL.h>
|
||||
#include <SDL2/SDL_image.h>
|
||||
}
|
||||
|
||||
#include "2d.h"
|
||||
#include <fmt/core.h>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
namespace arras {
|
||||
|
||||
struct Color {
|
||||
uint8_t r, g, b, a;
|
||||
|
||||
Color();
|
||||
Color(uint8_t r, uint8_t g, uint8_t b, uint8_t a);
|
||||
};
|
||||
|
||||
std::shared_ptr<SDL_Rect> makeRect(int x, int y, int w, int h);
|
||||
std::shared_ptr<SDL_FRect> makeFRect(const arras::Vec2& pos, const arras::Vec2& size);
|
||||
|
||||
class RenderSurface {
|
||||
public:
|
||||
RenderSurface(RenderSurface&& rs);
|
||||
RenderSurface(const RenderSurface& rs);
|
||||
explicit RenderSurface(SDL_RWops* src);
|
||||
RenderSurface(int width, int height);
|
||||
|
||||
~RenderSurface();
|
||||
|
||||
SDL_Surface* nativeHandle() const noexcept;
|
||||
|
||||
int width() const noexcept;
|
||||
int height() const noexcept;
|
||||
|
||||
private:
|
||||
SDL_Surface* s;
|
||||
};
|
||||
|
||||
class RenderWindow;
|
||||
|
||||
class RenderTexture {
|
||||
RenderTexture(const RenderTexture&) = delete;
|
||||
RenderTexture& operator=(const RenderTexture&) = delete;
|
||||
|
||||
public:
|
||||
RenderTexture(RenderTexture&& rt);
|
||||
RenderTexture(const RenderWindow& rd, SDL_RWops* src);
|
||||
RenderTexture(const RenderWindow& rd, const RenderSurface& rs);
|
||||
|
||||
~RenderTexture();
|
||||
|
||||
SDL_Texture* nativeHandle() const noexcept;
|
||||
|
||||
int width() const noexcept;
|
||||
int height() const noexcept;
|
||||
|
||||
std::pair<int, int> calcRenderSize(int w, int h) const noexcept;
|
||||
|
||||
private:
|
||||
SDL_Texture* t;
|
||||
int w, h;
|
||||
};
|
||||
|
||||
class RenderWindow {
|
||||
RenderWindow(const RenderWindow&) = delete;
|
||||
RenderWindow& operator=(const RenderWindow&) = delete;
|
||||
|
||||
public:
|
||||
RenderWindow(RenderWindow&& rd);
|
||||
RenderWindow(int w, int h);
|
||||
|
||||
~RenderWindow();
|
||||
|
||||
SDL_Renderer* nativeHandle() const noexcept;
|
||||
|
||||
void renderTextureAt(const RenderTexture& t, const Vec2& pos) const;
|
||||
void renderTextureAt(const RenderTexture& t, const Vec2& pos, const Vec2& direction) const;
|
||||
|
||||
void present() const noexcept;
|
||||
void clear(const Color& c = {}) const;
|
||||
|
||||
int width() const noexcept;
|
||||
int height() const noexcept;
|
||||
|
||||
private:
|
||||
int w, h;
|
||||
SDL_Window* window;
|
||||
SDL_Renderer* renderer;
|
||||
};
|
||||
} // namespace arras
|
||||
|
||||
#endif
|
32
main.cpp
Normal file
32
main.cpp
Normal file
@ -0,0 +1,32 @@
|
||||
#include "2d.h"
|
||||
#include "render.h"
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <cmath>
|
||||
|
||||
int main() {
|
||||
arras::RenderWindow w(2360, 1240);
|
||||
arras::RenderTexture t(w, SDL_RWFromFile("assets/basic.png", "rb"));
|
||||
std::printf("%d %d\n", t.width(), t.height());
|
||||
|
||||
const float PI = acos(-1);
|
||||
arras::Vec2 d{1, 0}, v{std::cos(PI / 300), std::sin(PI / 300)};
|
||||
SDL_Event evt;
|
||||
while (true) {
|
||||
while (SDL_PollEvent(&evt)) {
|
||||
if (evt.type == SDL_QUIT) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
w.clear();
|
||||
w.renderTextureAt(t, {200, 200}, d);
|
||||
w.present();
|
||||
d *= v;
|
||||
// pos += v;
|
||||
// if (pos.real() > 1000 || pos.real() < 200) {
|
||||
// v *= -1;
|
||||
// }
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(16));
|
||||
}
|
||||
}
|
32
objects.hpp
Normal file
32
objects.hpp
Normal file
@ -0,0 +1,32 @@
|
||||
#ifndef HEADER_OBJECTS_HPP
|
||||
#define HEADER_OBJECTS_HPP
|
||||
|
||||
#include "2d.h"
|
||||
#include <entt/entt.hpp>
|
||||
|
||||
namespace arras {
|
||||
|
||||
struct Transform {
|
||||
Vec2 position;
|
||||
};
|
||||
|
||||
struct Velocity {
|
||||
Vec2 velocity;
|
||||
};
|
||||
|
||||
struct EngineAcceleration {
|
||||
Vec2 ideal_velocity;
|
||||
Float acceleration;
|
||||
};
|
||||
|
||||
inline entt::entity makeTank(entt::registry &r, Vec2 position) {
|
||||
const auto e = r.create();
|
||||
r.emplace<Transform>(e, Transform{position});
|
||||
r.emplace<Velocity>(e, Velocity{{0, 0}});
|
||||
r.emplace<EngineAcceleration>(e, EngineAcceleration{{0, 0}, 1});
|
||||
return e;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
4930
package-lock.json
generated
4930
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
27
package.json
27
package.json
@ -1,27 +0,0 @@
|
||||
{
|
||||
"name": "openarras",
|
||||
"version": "0.1.0",
|
||||
"description": "",
|
||||
"source": "client/index.html",
|
||||
"entry": "src/main.js",
|
||||
"scripts": {
|
||||
"test": "mocha tests/"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.gzezfisher.top/szTom/openarras.git"
|
||||
},
|
||||
"author": "szdytom <szdytom@163.com>",
|
||||
"license": "Apache-2.0",
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"buffer": "^6.0.3",
|
||||
"mocha": "^10.2.0",
|
||||
"parcel": "^2.9.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^4.18.2",
|
||||
"socket.io": "^4.7.1",
|
||||
"socket.io-client": "^4.7.1"
|
||||
}
|
||||
}
|
123
src/2d.js
123
src/2d.js
@ -1,123 +0,0 @@
|
||||
function sqr(x) {
|
||||
return x * x;
|
||||
}
|
||||
|
||||
export class Circle {
|
||||
constructor(o, r) {
|
||||
this.o = o;
|
||||
this.r = r;
|
||||
}
|
||||
|
||||
clone() {
|
||||
return new Circle(this.o.clone(), this.r);
|
||||
}
|
||||
|
||||
equal(c) {
|
||||
return this.o.equal(c.o) && this.r == c.r;
|
||||
}
|
||||
|
||||
static intersect(a, b) {
|
||||
return a.o.sub(b.o).norm() <= sqr(a.r + b.r);
|
||||
}
|
||||
|
||||
static collisionElimation(a, b) {
|
||||
if (!Circle.intersect(a, b)) { return false; }
|
||||
const d = a.o.distanceTo(b.o);
|
||||
const ad = (sqr(a.r) - sqr(b.r) + sqr(d)) / (2 * d);
|
||||
const bd = d - ad;
|
||||
const v = b.o.sub(a.o);
|
||||
a.o.subTo(v.adjust(a.r - ad));
|
||||
b.o.addTo(v.adjust(b.r - bd));
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
export class Vec2 {
|
||||
constructor(x, y) {
|
||||
this.x = x || 0;
|
||||
this.y = y || 0;
|
||||
}
|
||||
|
||||
adjust(len) {
|
||||
return this.mul(len / this.abs());
|
||||
}
|
||||
|
||||
distanceTo(b) {
|
||||
return this.sub(b).abs();
|
||||
}
|
||||
|
||||
add(y) {
|
||||
return new Vec2(this.x + y.x, this.y + y.y);
|
||||
}
|
||||
|
||||
sub(y) {
|
||||
return new Vec2(this.x - y.x, this.y - y.y);
|
||||
}
|
||||
|
||||
mul(k) {
|
||||
return new Vec2(this.x * k, this.y * k);
|
||||
}
|
||||
|
||||
conj() {
|
||||
return new Vec2(this.x, -this.y);
|
||||
}
|
||||
|
||||
yx() {
|
||||
return new Vec2(this.y, this.x);
|
||||
}
|
||||
|
||||
complexMul(y) {
|
||||
return new Vec2(this.x * y.x - this.y * y.y, this.x * y.y + this.y * y.x);
|
||||
}
|
||||
|
||||
complexInv() {
|
||||
return this.conj().mul(1 / this.norm());
|
||||
}
|
||||
|
||||
complexDiv(y) {
|
||||
return this.complexMul(y.complexInv());
|
||||
}
|
||||
|
||||
addTo(y) {
|
||||
this.x += y.x;
|
||||
this.y += y.y;
|
||||
}
|
||||
|
||||
subTo(y) {
|
||||
this.x -= y.x;
|
||||
this.y -= y.y;
|
||||
}
|
||||
|
||||
mulTo(k) {
|
||||
this.x *= k;
|
||||
this.y *= k;
|
||||
}
|
||||
|
||||
equal(y) {
|
||||
return this.x == y.x && this.y == y.y;
|
||||
}
|
||||
|
||||
arg() {
|
||||
return Math.atan2(this.y, this.x);
|
||||
}
|
||||
|
||||
abs() {
|
||||
return Math.sqrt(this.norm());
|
||||
}
|
||||
|
||||
norm() {
|
||||
return sqr(this.x) + sqr(this.y);
|
||||
}
|
||||
|
||||
clone() {
|
||||
return new Vec2(this.x, this.y);
|
||||
}
|
||||
|
||||
dot(y) {
|
||||
return this.x * y.x + this.y * y.y;
|
||||
}
|
||||
|
||||
cross(y) {
|
||||
return this.x * y.y - this.y * y.x;
|
||||
}
|
||||
};
|
@ -1,18 +0,0 @@
|
||||
import { Circle } from '../src/2d.js';
|
||||
|
||||
export class Motion {
|
||||
constructor(p, v) {
|
||||
this.position = p;
|
||||
this.velocity = v;
|
||||
}
|
||||
};
|
||||
|
||||
export class CollisionBox {
|
||||
constructor(r) {
|
||||
this.r = r;
|
||||
}
|
||||
|
||||
shape(o) {
|
||||
return new Circle(o, this.r);
|
||||
}
|
||||
};
|
124
src/ecs.js
124
src/ecs.js
@ -1,124 +0,0 @@
|
||||
import { TreapSet } from "./misc/treap.js";
|
||||
|
||||
export class EntityRegistry {
|
||||
constructor() {
|
||||
this.free_ids = new TreapSet();
|
||||
this.signatures = [];
|
||||
this.components = [];
|
||||
this.component_map = new Map();
|
||||
}
|
||||
|
||||
requireComponentList(cname) {
|
||||
if (!this.component_map.has(cname)) {
|
||||
this.component_map.set(cname, this.components.length);
|
||||
this.components.push([]);
|
||||
}
|
||||
return this.components[this.component_map.get(cname)];
|
||||
}
|
||||
|
||||
componentListLowerBound(cl, entity) {
|
||||
if (cl.length === 0) {
|
||||
return 0;
|
||||
} else if (cl[cl.length - 1].__owner_entity < entity) {
|
||||
return cl.length;
|
||||
}
|
||||
|
||||
let l = 0, r = cl.length - 1, p = 0;
|
||||
while (l <= r) {
|
||||
let mid = (l + r) >>> 1;
|
||||
if (cl[mid].__owner_entity < entity) {
|
||||
l = mid + 1;
|
||||
} else {
|
||||
p = mid;
|
||||
r = mid - 1;
|
||||
}
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
assign(entity, cv) {
|
||||
const cname = cv.constructor.name;
|
||||
const cl = this.requireComponentList(cname);
|
||||
|
||||
cv.__owner_entity = entity;
|
||||
const p = this.componentListLowerBound(cl, entity);
|
||||
|
||||
if (cl[p]?.__owner_entity === entity) {
|
||||
cl[p] = cv;
|
||||
return this;
|
||||
}
|
||||
|
||||
cl.splice(p, 0, cv);
|
||||
this.signatures[entity][this.component_map.get(cname)] = p;
|
||||
return this;
|
||||
}
|
||||
|
||||
create() {
|
||||
let id = this.free_ids.takeInstance();
|
||||
if (id == null) {
|
||||
id = this.signatures.length;
|
||||
} else {
|
||||
this.free_ids.erase(id);
|
||||
}
|
||||
|
||||
this.signatures[id] = [];
|
||||
return id;
|
||||
}
|
||||
|
||||
destory(entity) {
|
||||
for (let i in this.signatures[entity]) {
|
||||
const p = this.signatures[entity][i];
|
||||
this.components[i].splice(p, 1);
|
||||
}
|
||||
|
||||
this.signatures[entity] = undefined;
|
||||
this.free_ids.insertRaw(entity);
|
||||
return this;
|
||||
}
|
||||
|
||||
getRaw(entity, cid) {
|
||||
return this.components[cid][this.signatures[entity][cid]];
|
||||
}
|
||||
|
||||
get(entity, ctype) {
|
||||
if (!this.component_map.has(ctype.name)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const cid = this.component_map.get(ctype.name);
|
||||
if (this.signatures[entity][cid] == null) {
|
||||
return null;
|
||||
}
|
||||
return this.getRaw(entity, cid);
|
||||
}
|
||||
|
||||
forEach(rc, func) {
|
||||
if (rc.length === 0) {
|
||||
for (let i = 0; i < this.signatures.length; ++i) {
|
||||
if (this.signatures[i]) {
|
||||
func.call(this, i);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
const rcid = rc.map(ctype => this.component_map.get(ctype.name));
|
||||
|
||||
let p, v = Infinity;
|
||||
for (let cid of rcid) {
|
||||
const cl = this.components[cid];
|
||||
if (cl.length < v) {
|
||||
v = cl.length;
|
||||
p = cid;
|
||||
}
|
||||
}
|
||||
|
||||
for (let c of this.components[p]) {
|
||||
const e = c.__owner_entity;
|
||||
if (rcid.every(cid => this.signatures[e][cid] != null)) {
|
||||
func.apply(this, [e].concat(rcid.map(cid => this.getRaw(e, cid))));
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
};
|
67
src/main.js
67
src/main.js
@ -1,67 +0,0 @@
|
||||
import { Vec2 } from '../src/2d.js';
|
||||
import { Motion, CollisionBox } from '../src/components.js';
|
||||
import { EntityRegistry } from '../src/ecs.js';
|
||||
import {Server} from 'socket.io';
|
||||
import Express from 'express';
|
||||
import http_server from 'http';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
const app = Express();
|
||||
const http = new http_server.Server(app);
|
||||
const io = new Server(http);
|
||||
const connections = new Set();
|
||||
|
||||
io.on("connection", socket => {
|
||||
connections.add(socket);
|
||||
console.log(`Connected client ${socket.id}.`);
|
||||
|
||||
socket.on('time-sync', callback => {
|
||||
callback(performance.now());
|
||||
});
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
connections.delete(socket);
|
||||
});
|
||||
});
|
||||
|
||||
function main() {
|
||||
app.use('/', Express.static(path.join(__dirname, '../dist')));
|
||||
|
||||
const server_port = parseInt(process.env.PORT) | 3000;
|
||||
http.listen(server_port, () => {
|
||||
console.log(`Server started on port ${server_port}.`);
|
||||
});
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
console.log('\nShutting server...');
|
||||
connections.forEach(socket => {
|
||||
socket.disconnect();
|
||||
console.log(`Disconnected socket ${socket.id};`);
|
||||
})
|
||||
|
||||
http.close(() => {
|
||||
console.log('Terminated.');
|
||||
process.exit(0);
|
||||
});
|
||||
});
|
||||
|
||||
let registry = new EntityRegistry();
|
||||
let tank = registry.create();
|
||||
registry.assign(tank, new Motion(new Vec2(0, 0), new Vec2(1, 1)));
|
||||
registry.assign(tank, new CollisionBox(1));
|
||||
|
||||
setInterval(() => {
|
||||
registry.forEach([Motion], (id, motion) => {
|
||||
motion.position.addTo(motion.velocity);
|
||||
});
|
||||
}, 1000/60);
|
||||
|
||||
setInterval(() => {
|
||||
io.emit("update", registry.get(tank, Motion))
|
||||
}, 50);
|
||||
}
|
||||
|
||||
main();
|
@ -1,51 +0,0 @@
|
||||
export class PriorityQueue {
|
||||
constructor() {
|
||||
this.val = [null];
|
||||
}
|
||||
|
||||
push(value, priority) {
|
||||
if (priority == null) {
|
||||
priority = value;
|
||||
}
|
||||
|
||||
this.val.push([value, priority]);
|
||||
let id = this.val.length - 1;
|
||||
while (id > 1 && this.val[id][1] > this.val[id >>> 1][1]) {
|
||||
const kid = id >>> 1;
|
||||
[this.val[id], this.val[kid]] = [this.val[kid], this.val[id]];
|
||||
id = kid;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
pop() {
|
||||
let lv = this.val.pop();
|
||||
if (this.val.length == 1) { return lv; }
|
||||
|
||||
let res = this.val[1];
|
||||
this.val[1] = lv;
|
||||
let id = 1;
|
||||
while (id * 2 < this.val.length) {
|
||||
if (this.val[id][1] > this.val[id * 2][1] && (id * 2 + 1 >= this.val.length || this.val[id][1] > this.val[id * 2 + 1][1])) {
|
||||
break;
|
||||
}
|
||||
|
||||
let kid = (id * 2 + 1 >= this.val.length || this.val[id * 2][1] > this.val[id * 2 + 1][1]) ? id * 2 : id * 2 + 1;
|
||||
[this.val[id], this.val[kid]] = [this.val[kid], this.val[id]];
|
||||
id = kid;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
top() {
|
||||
return this.val[1];
|
||||
}
|
||||
|
||||
size() {
|
||||
return this.val.length - 1;
|
||||
}
|
||||
|
||||
empty() {
|
||||
return this.size() == 0;
|
||||
}
|
||||
}
|
@ -1,105 +0,0 @@
|
||||
// FHQ Treap impl a set
|
||||
|
||||
class TreapNode {
|
||||
constructor(val) {
|
||||
this.value = val;
|
||||
this.left = null;
|
||||
this.right = null;
|
||||
this.sz = 1;
|
||||
this.rd = Math.random();
|
||||
}
|
||||
|
||||
update() {
|
||||
this.sz = (this.left?.sz || 0) + (this.right?.sz || 0) + 1;
|
||||
}
|
||||
|
||||
static splitBySize(rt, k) {
|
||||
if (rt == null) {
|
||||
return [null, null];
|
||||
}
|
||||
|
||||
let x, y;
|
||||
if ((rt.left?.sz || 0) + 1 <= k) {
|
||||
x = rt;
|
||||
[rt.right, y] = TreapNode.splitBySize(rt.right, k - (rt.left?.sz || 0) - 1);
|
||||
} else {
|
||||
y = rt;
|
||||
[x, rt.left] = TreapNode.splitBySize(rt.left, k);
|
||||
}
|
||||
rt.update();
|
||||
return [x, y];
|
||||
}
|
||||
|
||||
static splitByValue(rt, v) {
|
||||
if (rt == null) {
|
||||
return [null, null];
|
||||
}
|
||||
|
||||
let x, y;
|
||||
if (rt.value <= v) {
|
||||
x = rt;
|
||||
[rt.right, y] = TreapNode.splitByValue(rt.right, v);
|
||||
} else {
|
||||
y = rt;
|
||||
[x, rt.left] = TreapNode.splitByValue(rt.left, v);
|
||||
}
|
||||
rt.update();
|
||||
return [x, y];
|
||||
}
|
||||
|
||||
static merge(x, y) {
|
||||
if (x == null) {
|
||||
return y;
|
||||
} else if (y == null) {
|
||||
return x;
|
||||
}
|
||||
|
||||
if (x.rd < y.rd) {
|
||||
y.left = TreapNode.merge(x, y.left);
|
||||
y.update();
|
||||
return y;
|
||||
}
|
||||
|
||||
x.right = TreapNode.merge(x.right, y);
|
||||
x.update();
|
||||
return x;
|
||||
}
|
||||
|
||||
static forEach(rt, func) {
|
||||
if (rt == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
TreapNode.forEach(rt.left, func);
|
||||
func(rt.value);
|
||||
TreapNode.forEach(rt.right, func);
|
||||
}
|
||||
}
|
||||
|
||||
export class TreapSet {
|
||||
constructor() {
|
||||
this.root = null;
|
||||
}
|
||||
|
||||
insertRaw(v) {
|
||||
let [x, y] = TreapNode.splitByValue(this.root, v);
|
||||
this.root = TreapNode.merge(TreapNode.merge(x, new TreapNode(v)), y);
|
||||
return this;
|
||||
}
|
||||
|
||||
erase(v) {
|
||||
let [x, p] = TreapNode.splitByValue(this.root, v - 1);
|
||||
let [_, z] = TreapNode.splitByValue(p, v);
|
||||
this.root = TreapNode.merge(x, z);
|
||||
return this;
|
||||
}
|
||||
|
||||
takeInstance() {
|
||||
return this.root == null ? null : this.root.value;
|
||||
}
|
||||
|
||||
forEach(func) {
|
||||
TreapNode.forEach(this.root, func);
|
||||
return this;
|
||||
}
|
||||
}
|
42
src/render/misc.cpp
Normal file
42
src/render/misc.cpp
Normal file
@ -0,0 +1,42 @@
|
||||
#include "render.h"
|
||||
|
||||
namespace {
|
||||
|
||||
class SDLLoader {
|
||||
private:
|
||||
SDLLoader() {
|
||||
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS);
|
||||
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "best");
|
||||
IMG_Init(IMG_INIT_PNG);
|
||||
}
|
||||
|
||||
~SDLLoader() {
|
||||
IMG_Quit();
|
||||
SDL_Quit();
|
||||
}
|
||||
|
||||
static const SDLLoader _;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
arras::Color::Color(): r(255), g(255), b(255), a(255) {}
|
||||
arras::Color::Color(uint8_t r, uint8_t g, uint8_t b, uint8_t a): r(r), g(g), b(b), a(a) {}
|
||||
|
||||
std::shared_ptr<SDL_Rect> arras::makeRect(int x, int y, int w, int h) {
|
||||
auto res = std::make_shared<SDL_Rect>();
|
||||
res->x = x;
|
||||
res->y = y;
|
||||
res->w = w;
|
||||
res->h = h;
|
||||
return res;
|
||||
}
|
||||
|
||||
std::shared_ptr<SDL_FRect> arras::makeFRect(const arras::Vec2& pos, const arras::Vec2& size) {
|
||||
auto res = std::make_shared<SDL_FRect>();
|
||||
res->x = pos.real();
|
||||
res->y = pos.imag();
|
||||
res->w = size.real();
|
||||
res->h = size.imag();
|
||||
return res;
|
||||
}
|
53
src/render/surface.cpp
Normal file
53
src/render/surface.cpp
Normal file
@ -0,0 +1,53 @@
|
||||
#include "render.h"
|
||||
|
||||
using namespace arras;
|
||||
|
||||
RenderSurface::RenderSurface(RenderSurface&& rs): s(rs.s) {
|
||||
rs.s = nullptr;
|
||||
}
|
||||
|
||||
RenderSurface::RenderSurface(const RenderSurface& rs) {
|
||||
s = SDL_CreateRGBSurfaceWithFormat(0, rs.width(), rs.height(), 32, SDL_PIXELFORMAT_RGBA8888);
|
||||
if (!s) {
|
||||
throw std::runtime_error(
|
||||
fmt::format("SDL_CreateRGBSurfaceWithFormat() failed: {}", SDL_GetError()));
|
||||
}
|
||||
if (SDL_BlitSurface(rs.nativeHandle(),
|
||||
nullptr,
|
||||
s,
|
||||
makeRect(0, 0, rs.width(), rs.height()).get())
|
||||
!= 0) {
|
||||
throw std::runtime_error(fmt::format("SDL_BlitSurface() failed: {}", SDL_GetError()));
|
||||
}
|
||||
}
|
||||
|
||||
RenderSurface::RenderSurface(SDL_RWops* src) {
|
||||
s = IMG_Load_RW(src, 1);
|
||||
if (!s) {
|
||||
throw std::runtime_error(fmt::format("IMG_Load_RW() failed: {}", IMG_GetError()));
|
||||
}
|
||||
}
|
||||
|
||||
RenderSurface::RenderSurface(int width, int height) {
|
||||
s = SDL_CreateRGBSurfaceWithFormat(0, width, height, 32, SDL_PIXELFORMAT_RGBA8888);
|
||||
if (!s) {
|
||||
throw std::runtime_error(
|
||||
fmt::format("SDL_CreateRGBSurfaceWithFormat() failed: {}", SDL_GetError()));
|
||||
}
|
||||
}
|
||||
|
||||
RenderSurface ::~RenderSurface() {
|
||||
SDL_FreeSurface(s);
|
||||
}
|
||||
|
||||
SDL_Surface* RenderSurface::nativeHandle() const noexcept {
|
||||
return s;
|
||||
}
|
||||
|
||||
int RenderSurface::width() const noexcept {
|
||||
return s->w;
|
||||
}
|
||||
|
||||
int RenderSurface::height() const noexcept {
|
||||
return s->h;
|
||||
}
|
63
src/render/texture.cpp
Normal file
63
src/render/texture.cpp
Normal file
@ -0,0 +1,63 @@
|
||||
|
||||
#include "render.h"
|
||||
|
||||
using namespace arras;
|
||||
|
||||
RenderTexture::RenderTexture(RenderTexture&& rt): t(rt.t) {
|
||||
rt.t = nullptr;
|
||||
}
|
||||
|
||||
RenderTexture::RenderTexture(const RenderWindow& rd, SDL_RWops* src) {
|
||||
t = IMG_LoadTexture_RW(rd.nativeHandle(), src, 1);
|
||||
if (!t) {
|
||||
throw std::runtime_error(fmt::format("IMG_LoadTexture_RW() failed: {}", IMG_GetError()));
|
||||
}
|
||||
if (SDL_QueryTexture(t, nullptr, nullptr, &w, &h) != 0) {
|
||||
throw std::runtime_error(fmt::format("SDL_QueryTexture() failed: {}", SDL_GetError()));
|
||||
}
|
||||
}
|
||||
|
||||
RenderTexture::RenderTexture(const RenderWindow& rd, const RenderSurface& rs) {
|
||||
t = SDL_CreateTextureFromSurface(rd.nativeHandle(), rs.nativeHandle());
|
||||
if (!t) {
|
||||
throw std::runtime_error(
|
||||
fmt::format("SDL_CreateRGBSurfaceWithFormat() failed: {}", SDL_GetError()));
|
||||
}
|
||||
if (SDL_QueryTexture(t, nullptr, nullptr, &w, &h)) {
|
||||
throw std::runtime_error(fmt::format("SDL_QueryTexture() failed: {}", SDL_GetError()));
|
||||
}
|
||||
}
|
||||
|
||||
RenderTexture::~RenderTexture() {
|
||||
SDL_DestroyTexture(t);
|
||||
}
|
||||
|
||||
SDL_Texture* RenderTexture::nativeHandle() const noexcept {
|
||||
return t;
|
||||
}
|
||||
|
||||
int RenderTexture::width() const noexcept {
|
||||
return w;
|
||||
}
|
||||
|
||||
int RenderTexture::height() const noexcept {
|
||||
return h;
|
||||
}
|
||||
|
||||
std::pair<int, int> RenderTexture::calcRenderSize(int w, int h) const noexcept {
|
||||
int rw, rh;
|
||||
if (w == 0 && h == 0) {
|
||||
rw = width();
|
||||
rh = height();
|
||||
} else if (w == 0) {
|
||||
rw = (h * width() / height());
|
||||
rh = h;
|
||||
} else if (h == 0) {
|
||||
rw = w;
|
||||
rh = (w * height() / width());
|
||||
} else {
|
||||
rw = w;
|
||||
rh = h;
|
||||
}
|
||||
return {rw, rh};
|
||||
}
|
76
src/render/window.cpp
Normal file
76
src/render/window.cpp
Normal file
@ -0,0 +1,76 @@
|
||||
#ifndef HEADER_ARRAS_RENDER_WINDOW_HPP
|
||||
#define HEADER_ARRAS_RENDER_WINDOW_HPP
|
||||
|
||||
#include "render.h"
|
||||
#define Pi 3.14159265358979323846
|
||||
|
||||
using namespace arras;
|
||||
|
||||
RenderWindow::RenderWindow(RenderWindow&& rd)
|
||||
: w(rd.w), h(rd.h), window(rd.window), renderer(rd.renderer) {
|
||||
rd.renderer = nullptr;
|
||||
rd.window = nullptr;
|
||||
}
|
||||
|
||||
RenderWindow::RenderWindow(int w, int h): w(w), h(h) {
|
||||
if (SDL_CreateWindowAndRenderer(w, h, SDL_WINDOW_SHOWN | SDL_WINDOW_ALLOW_HIGHDPI, &window, &renderer) != 0) {
|
||||
throw std::runtime_error(
|
||||
fmt::format("SDL_CreateWindowAndRenderer() failed: {}", SDL_GetError()));
|
||||
}
|
||||
}
|
||||
|
||||
RenderWindow::~RenderWindow() {
|
||||
SDL_DestroyRenderer(renderer);
|
||||
SDL_DestroyWindow(window);
|
||||
}
|
||||
|
||||
SDL_Renderer* RenderWindow::nativeHandle() const noexcept {
|
||||
return renderer;
|
||||
}
|
||||
|
||||
void RenderWindow::renderTextureAt(const RenderTexture& t, const Vec2& pos) const {
|
||||
if (SDL_RenderCopyF(renderer,
|
||||
t.nativeHandle(),
|
||||
NULL,
|
||||
makeFRect(pos, {t.width(), t.height()}).get())) {
|
||||
throw std::runtime_error(fmt::format("SDL_RenderCopy() failed: {}", SDL_GetError()));
|
||||
}
|
||||
}
|
||||
|
||||
void RenderWindow::renderTextureAt(const RenderTexture& t,
|
||||
const Vec2& pos,
|
||||
const Vec2& direction) const {
|
||||
if (SDL_RenderCopyExF(renderer,
|
||||
t.nativeHandle(),
|
||||
NULL,
|
||||
makeFRect(pos, {t.width(), t.height()}).get(),
|
||||
-asAngle(direction) * 180 / Pi,
|
||||
nullptr,
|
||||
SDL_FLIP_NONE)) {
|
||||
throw std::runtime_error(fmt::format("SDL_RenderCopy() failed: {}", SDL_GetError()));
|
||||
}
|
||||
}
|
||||
|
||||
void RenderWindow::present() const noexcept {
|
||||
SDL_RenderPresent(renderer);
|
||||
}
|
||||
|
||||
void RenderWindow::clear(const Color& c) const {
|
||||
if (SDL_SetRenderDrawColor(renderer, c.r, c.g, c.b, c.a)) {
|
||||
throw std::runtime_error(
|
||||
fmt::format("SDL_SetRenderDrawColor() failed: {}", SDL_GetError()));
|
||||
}
|
||||
if (SDL_RenderClear(renderer)) {
|
||||
throw std::runtime_error(fmt::format("SDL_RenderClear() failed: {}", SDL_GetError()));
|
||||
}
|
||||
}
|
||||
|
||||
int RenderWindow::width() const noexcept {
|
||||
return w;
|
||||
}
|
||||
|
||||
int RenderWindow::height() const noexcept {
|
||||
return h;
|
||||
}
|
||||
|
||||
#endif
|
60
test/cases/2d.cpp
Normal file
60
test/cases/2d.cpp
Normal file
@ -0,0 +1,60 @@
|
||||
#include "2d.h"
|
||||
#include "test/u.hpp"
|
||||
#include <functional>
|
||||
#include <initializer_list>
|
||||
|
||||
namespace {
|
||||
|
||||
AddTestCase _(1, "2d.hpp", [](TestCase& t) {
|
||||
using namespace arras;
|
||||
t.expectEq<Float>([] { return cross({1, 2}, {4, 1}); }, -7);
|
||||
t.expectEq<Float>([] { return cross({.5, 2.5}, {4, 1.5}); }, -9.25);
|
||||
|
||||
t.expectEq<Float>([] { return dot({1, 2}, {4, 1}); }, 6);
|
||||
|
||||
t.expectTrue([] { return Circle({0, 0}, 2).contain({1, 1}); });
|
||||
t.expectTrue([] { return Circle({0, 0}, 1).contain({0, 1}); });
|
||||
t.expectFalse([] { return Circle({1, 0}, 1.4).contain({0, -1}); });
|
||||
t.expectTrue([] { return Circle({1, 1}, 1.42).contain({0, 0}); });
|
||||
t.expectTrue([] { return Circle({1.83649, -.12711}, .05484).contain({1.82541, -.1019}); });
|
||||
|
||||
t.expectEq<Vec2>([] { return Polygon<3>{{0, 0}, {1, 0}, {1, 1}}.vertex[0]; }, {0, 0});
|
||||
|
||||
t.expectTrue([] { return Polygon<3>{{0, 0}, {1, 0}, {1, 1}}.contain({.1, .05}); });
|
||||
t.expectFalse([] { return Polygon<3>{{0, 0}, {1, 0}, {1, 1}}.contain({.1, .2}); });
|
||||
|
||||
Polygon<9> pA{{-2, -2}, {-1, -2}, {2, 0}, {1, -2}, {2, -1}, {2, -3}, {3, 1}, {1, 1}, {0, 0}};
|
||||
t.expectTrue([pA] { return pA.contain({1, 0}); });
|
||||
t.expectTrue([pA] { return pA.contain({0, 0}); });
|
||||
t.expectTrue([pA] { return pA.contain({0, -1}); });
|
||||
t.expectTrue([pA] { return pA.contain({1.52, -1.26}); });
|
||||
t.expectTrue([pA] { return pA.contain({2.16, -1.9}); });
|
||||
t.expectFalse([pA] { return pA.contain({1.28, -.8}); });
|
||||
t.expectFalse([pA] { return pA.contain({1.68, -1.94}); });
|
||||
t.expectFalse([pA] { return pA.contain({1.83649, -.12711}); });
|
||||
t.expectTrue([pA] { return pA.contain({1.82541, -.1019}); });
|
||||
|
||||
t.expectTrue([] { return intersect(Circle({1, 1}, 2), Circle({1, 1}, 1)); });
|
||||
t.expectTrue([] { return intersect(Circle({1, 1}, 1.5), Circle({3, 1}, 1.5)); });
|
||||
t.expectFalse([] { return intersect(Circle({1, 1}, 1.5), Circle({3, 4}, 1.5)); });
|
||||
t.expectFalse([] { return intersect(Circle({0, 0}, 1.5), Circle({-3, -4}, 1.5)); });
|
||||
|
||||
t.expectTrue([] { return intersect(Circle({1, 1}, 2), Polygon<3>{{0, 0}, {2, 2}, {0, 2}}); });
|
||||
t.expectTrue([] { return intersect(Polygon<3>{{0, 0}, {2, 2}, {0, 2}}, Circle({1, 1}, 2)); });
|
||||
|
||||
t.expectTrue([] { return intersect(Circle({0, 0}, 5), Polygon<4>{{-1, -1}, {-1, 1}, {1, 1}, {1, -1}}); });
|
||||
t.expectTrue([] { return intersect(Circle({0, 0}, 1), Polygon<4>{{-1, -1}, {-1, 1}, {1, 1}, {1, -1}}); });
|
||||
t.expectTrue([] { return intersect(Circle({0, 0}, 1), Polygon<4>{{-1, -1}, {-1, 1}, {1, 1}, {1, -1}}); });
|
||||
t.expectTrue([] { return intersect(Circle({0, 0}, 1), Polygon<3>{{-1, -1}, {-1, 1}, {1, 1}}); });
|
||||
t.expectFalse([] { return intersect(Circle({0, 0}, 2), Polygon<3>{{0, -3}, {14, -3}, {0, -5}}); });
|
||||
t.expectFalse([pA] { return intersect(Circle({-1, 1}, 1.4), pA); });
|
||||
t.expectTrue([pA] { return intersect(Circle({-3, -2}, 1.2), pA); });
|
||||
t.expectTrue([pA] { return intersect(Circle({1.83649, -.12711}, .05484), pA); });
|
||||
t.expectFalse([pA] { return intersect(Circle({1.5595, -.42437}, .08794), pA); });
|
||||
|
||||
t.expectTrue([] { return intersect(Polygon<4>{{3, 1}, {0, 1}, {0, 0}, {3, 0}}
|
||||
, Polygon<4>{{.1, .1}, {.2, .1}, {.2, .2}, {.1, .2}}); });
|
||||
t.expectFalse([pA] { return intersect(pA, Polygon<3>{{1.5595, -.42437}, {1.64733, -.41987}, {1.83649, -.12711}}); });
|
||||
t.expectTrue([pA] { return intersect(pA, Polygon<3>{{1.5595, -.42437}, {1.64733, -.41987}, {1.82541, -.1019}}); });
|
||||
});
|
||||
}
|
10
test/cases/objects.cpp
Normal file
10
test/cases/objects.cpp
Normal file
@ -0,0 +1,10 @@
|
||||
#include "objects.hpp"
|
||||
#include "test/u.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
AddTestCase _(1, "objects.hpp", [](TestCase& t) {
|
||||
|
||||
});
|
||||
|
||||
}
|
19
test/cases/provider.cpp
Normal file
19
test/cases/provider.cpp
Normal file
@ -0,0 +1,19 @@
|
||||
#include "test/u.hpp"
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
namespace {
|
||||
|
||||
AddTestCase _(0, "tester", [](TestCase& t) {
|
||||
t.expectTrue([] { return true; });
|
||||
t.expectFalse([] { return false; });
|
||||
t.expectTrue([] {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
||||
return true;
|
||||
});
|
||||
|
||||
t.expectEq<int>([] { return 42; }, 42);
|
||||
t.expectEq<float>([] { return 10 + 1e-7; }, 10);
|
||||
t.expectEq<double>([] { return 10 + 1e-10; }, 10);
|
||||
});
|
||||
}
|
5
test/main.cpp
Normal file
5
test/main.cpp
Normal file
@ -0,0 +1,5 @@
|
||||
#include "test/u.hpp"
|
||||
|
||||
int main() {
|
||||
return TestSuit::getInstance()->evalTestCases();
|
||||
}
|
204
test/u.hpp
Normal file
204
test/u.hpp
Normal file
@ -0,0 +1,204 @@
|
||||
#ifndef HEADER_TEST_U_HPP
|
||||
#define HEADER_TEST_U_HPP
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <exception>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <type_traits>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
template<typename T>
|
||||
struct TestDefaultEps {};
|
||||
|
||||
template<>
|
||||
struct TestDefaultEps<float> {
|
||||
static constexpr float value = 1e-6;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct TestDefaultEps<double> {
|
||||
static constexpr double value = 1e-9;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct TestDefaultEps<long double> {
|
||||
static constexpr long double value = 1e-12;
|
||||
};
|
||||
|
||||
class TestCase {
|
||||
public:
|
||||
TestCase(int id, const char* title): ok(0), fail(0), n(0), ended(false), time_used(0) {
|
||||
std::printf("[%02d]Testing %s:\n", id, title);
|
||||
}
|
||||
|
||||
void expectTrue(std::function<bool()> func) noexcept {
|
||||
expectEq<bool>(func, true);
|
||||
}
|
||||
|
||||
void expectFalse(std::function<bool()> func) noexcept {
|
||||
expectEq<bool>(func, false);
|
||||
}
|
||||
|
||||
template<typename T, typename EPS = TestDefaultEps<T>>
|
||||
typename std::enable_if<std::is_floating_point<T>::value>::type expectEq(
|
||||
std::function<T()> func,
|
||||
T&& answer) noexcept {
|
||||
auto res = runTask(func);
|
||||
if (!res.has_value()) {
|
||||
return onError();
|
||||
}
|
||||
|
||||
if (std::abs(res.value() - answer) < EPS::value) {
|
||||
onPass();
|
||||
} else {
|
||||
onFail();
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
typename std::enable_if<!std::is_floating_point<T>::value>::type expectEq(
|
||||
std::function<T()> func,
|
||||
T&& answer) noexcept {
|
||||
auto res = runTask(func);
|
||||
if (!res.has_value()) {
|
||||
return onError();
|
||||
}
|
||||
|
||||
if (res.value() == answer) {
|
||||
onPass();
|
||||
} else {
|
||||
onFail();
|
||||
}
|
||||
}
|
||||
|
||||
void end() noexcept {
|
||||
if (ended) {
|
||||
return;
|
||||
}
|
||||
ended = true;
|
||||
|
||||
std::putchar('\n');
|
||||
for (int i = 0; i < 20; ++i) {
|
||||
std::putchar('-');
|
||||
}
|
||||
std::putchar('\n');
|
||||
if (fail) {
|
||||
if (fail == 1) {
|
||||
std::puts("1 test failed.");
|
||||
} else {
|
||||
std::printf("%d tests failed.\n", fail);
|
||||
}
|
||||
} else {
|
||||
if (ok == 1) {
|
||||
std::printf("Ok. 1 test passed");
|
||||
} else {
|
||||
std::printf("Ok. %d tests passed", ok);
|
||||
}
|
||||
std::printf(" in %lldms.\n", static_cast<long long>(time_used.count()));
|
||||
}
|
||||
}
|
||||
|
||||
bool hasFail() const noexcept {
|
||||
return fail > 0;
|
||||
}
|
||||
|
||||
private:
|
||||
void putchar(char c) noexcept {
|
||||
n += 1;
|
||||
if (n > 1 && n % 15 == 1) {
|
||||
std::putchar('\n');
|
||||
}
|
||||
std::putchar(c);
|
||||
}
|
||||
|
||||
void onPass() noexcept {
|
||||
ok += 1;
|
||||
putchar('.');
|
||||
}
|
||||
|
||||
void onFail() noexcept {
|
||||
fail += 1;
|
||||
putchar('F');
|
||||
}
|
||||
|
||||
void onError() noexcept {
|
||||
fail += 1;
|
||||
putchar('E');
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::optional<T> runTask(std::function<T()> func) noexcept {
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
try {
|
||||
T res = func();
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
auto dt = end - start;
|
||||
time_used += std::chrono::duration_cast<std::chrono::milliseconds>(dt);
|
||||
return {res};
|
||||
} catch (...) {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
int ok, fail, n;
|
||||
bool ended;
|
||||
std::chrono::milliseconds time_used;
|
||||
};
|
||||
|
||||
class TestSuit {
|
||||
TestSuit(const TestSuit&) = delete;
|
||||
TestSuit(TestSuit&&) = delete;
|
||||
|
||||
public:
|
||||
static TestSuit* getInstance() {
|
||||
if (!instance) {
|
||||
instance = new TestSuit();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
void addTestCase(int id, const char* title, std::function<void(TestCase&)> f) {
|
||||
tc.emplace_back(id, title, f);
|
||||
}
|
||||
|
||||
int evalTestCases() {
|
||||
std::sort(tc.begin(), tc.end(), [](const auto &x, const auto &y) {
|
||||
return std::get<0>(x) < std::get<0>(y);
|
||||
});
|
||||
|
||||
for (auto [id, title, func] : tc) {
|
||||
TestCase t(id, title);
|
||||
func(t);
|
||||
t.end();
|
||||
std::putchar('\n');
|
||||
if (t.hasFail()) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
tc.clear();
|
||||
return 0;
|
||||
}
|
||||
|
||||
private:
|
||||
static inline TestSuit* instance = nullptr;
|
||||
TestSuit() {}
|
||||
|
||||
std::vector<std::tuple<int, const char*, std::function<void(TestCase&)>>> tc;
|
||||
};
|
||||
|
||||
class AddTestCase {
|
||||
AddTestCase() = delete;
|
||||
AddTestCase(const AddTestCase&) = delete;
|
||||
AddTestCase(AddTestCase&&) = delete;
|
||||
|
||||
public:
|
||||
AddTestCase(int id, const char* tilte, std::function<void(TestCase&)> func) {
|
||||
TestSuit::getInstance()->addTestCase(id, tilte, func);
|
||||
}
|
||||
};
|
||||
|
||||
#endif // HEADER_TEST_U_HPP
|
@ -1,68 +0,0 @@
|
||||
import assert from 'node:assert';
|
||||
import { Vec2, Circle } from '../src/2d.js';
|
||||
|
||||
describe('Vec2', function () {
|
||||
it('construct', function() {
|
||||
let p = new Vec2(1, 2);
|
||||
assert.equal(p.x, 1);
|
||||
assert.equal(p.y, 2);
|
||||
});
|
||||
|
||||
it('equal', function() {
|
||||
assert.ok((new Vec2(1, 2)).equal(new Vec2(1, 2)));
|
||||
assert.ok(!(new Vec2(1, 2)).equal(new Vec2(1, 12)));
|
||||
});
|
||||
|
||||
it('vector calculation', function() {
|
||||
let p = new Vec2(2, 4), q = new Vec2(3, 1);
|
||||
assert.ok(p.add(q).equal(new Vec2(5, 5)));
|
||||
assert.ok(p.sub(q).equal(new Vec2(-1, 3)));
|
||||
assert.ok(p.mul(1.5).equal(new Vec2(3, 6)));
|
||||
assert.ok(p.yx().equal(new Vec2(4, 2)));
|
||||
});
|
||||
|
||||
it('vector calculation(TO)', function () {
|
||||
let p = new Vec2(2, 4), q = new Vec2(3, 1);
|
||||
p.addTo(q);
|
||||
assert.ok(p.equal(new Vec2(5, 5)));
|
||||
p.subTo(q);
|
||||
assert.ok(p.equal(new Vec2(2, 4)));
|
||||
p.mulTo(1.5);
|
||||
assert.ok(p.equal(new Vec2(3, 6)));
|
||||
});
|
||||
|
||||
it('abs & norm', function() {
|
||||
let p = new Vec2(3, 4);
|
||||
assert.equal(p.abs(), 5);
|
||||
assert.equal(p.norm(), 25);
|
||||
});
|
||||
|
||||
it('complex calculation', function() {
|
||||
let p = new Vec2(1, 4), q = new Vec2(0, 1);
|
||||
assert.ok(p.complexMul(q).equal(new Vec2(-4, 1)));
|
||||
assert.ok(p.complexDiv(q).equal(new Vec2(4, -1)));
|
||||
assert.equal(q.arg(), Math.PI / 2);
|
||||
});
|
||||
|
||||
it('dot & cross product', function() {
|
||||
let p = new Vec2(2, 4), q = new Vec2(3, 1);
|
||||
assert.equal(p.dot(q), 10);
|
||||
assert.equal(p.cross(q), -10);
|
||||
});
|
||||
|
||||
it('circle intersect', function() {
|
||||
let c1 = new Circle(new Vec2(0, 0), 1);
|
||||
let c2 = new Circle(new Vec2(0, 1.5), 1);
|
||||
let c3 = new Circle(new Vec2(2, 2), 1);
|
||||
assert.ok(Circle.intersect(c1, c2));
|
||||
assert.ok(!Circle.intersect(c1, c3));
|
||||
});
|
||||
|
||||
it('circle collision elimation', function() {
|
||||
let c1 = new Circle(new Vec2(0, 0), 1);
|
||||
let c2 = new Circle(new Vec2(0, 1.5), 1);
|
||||
assert.ok(Circle.collisionElimation(c1, c2));
|
||||
assert.ok(c1.equal(new Circle(new Vec2(0, -.25), 1)));
|
||||
assert.ok(c2.equal(new Circle(new Vec2(0, 1.75), 1)));
|
||||
});
|
||||
});
|
@ -1,59 +0,0 @@
|
||||
import assert from 'node:assert';
|
||||
import { EntityRegistry } from '../src/ecs.js';
|
||||
|
||||
class Position {
|
||||
constructor(x, y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
}
|
||||
|
||||
describe('ECS', function () {
|
||||
it('create', function () {
|
||||
let registry = new EntityRegistry();
|
||||
let id = registry.create();
|
||||
});
|
||||
|
||||
it('destory', function () {
|
||||
let registry = new EntityRegistry();
|
||||
let id0 = registry.create();
|
||||
let id1 = registry.destory(id0).create();
|
||||
assert.equal(id0, id1);
|
||||
});
|
||||
|
||||
it('component', function () {
|
||||
let registry = new EntityRegistry();
|
||||
let id3;
|
||||
for (let i = 0; i < 5; i += 1) {
|
||||
let e = registry.create();
|
||||
registry.assign(e, new Position(i, i));
|
||||
if (i == 3) {
|
||||
id3 = e;
|
||||
}
|
||||
}
|
||||
assert.equal(registry.get(id3, Position).x, 3);
|
||||
});
|
||||
|
||||
it('forEach', function () {
|
||||
let registry = new EntityRegistry();
|
||||
let id3;
|
||||
registry.create();
|
||||
registry.create();
|
||||
for (let i = 0; i < 5; i += 1) {
|
||||
let e = registry.create();
|
||||
registry.assign(e, new Position(i, i));
|
||||
if (i == 3) {
|
||||
id3 = e;
|
||||
}
|
||||
}
|
||||
let count = 0;
|
||||
registry.forEach([], () => {
|
||||
count += 1;
|
||||
});
|
||||
assert.equal(count, 7);
|
||||
registry.forEach([Position], (id, pos) => {
|
||||
pos.x += 1;
|
||||
});
|
||||
assert.equal(registry.get(id3, Position).x, 4);
|
||||
});
|
||||
});
|
@ -1,18 +0,0 @@
|
||||
import assert from 'node:assert';
|
||||
import { PriorityQueue } from '../src/misc/priority_queue.js';
|
||||
|
||||
describe('PriorityQueue', function () {
|
||||
it('everything', function() {
|
||||
let q = new PriorityQueue();
|
||||
q.push(1).push(2).push(-1).push('hi', 4);
|
||||
assert.equal(q.size(), 4);
|
||||
assert.equal(q.top()[0], 'hi');
|
||||
|
||||
assert.equal(q.pop()[0], 'hi');
|
||||
assert.equal(q.pop()[0], 2);
|
||||
assert.equal(q.pop()[0], 1);
|
||||
assert.equal(q.pop()[0], -1);
|
||||
|
||||
assert.ok(q.empty());
|
||||
});
|
||||
});
|
@ -1,25 +0,0 @@
|
||||
import assert from 'node:assert';
|
||||
import { TreapSet } from '../src/misc/treap.js';
|
||||
|
||||
describe('TreapSet', function () {
|
||||
it('hasInstance', function() {
|
||||
let t = new TreapSet();
|
||||
assert.equal(t.takeInstance(), null);
|
||||
t.insertRaw(2);
|
||||
assert.equal(t.takeInstance(), 2);
|
||||
});
|
||||
|
||||
it('erase', function () {
|
||||
let t = new TreapSet();
|
||||
t.insertRaw(2).insertRaw(3).erase(2);
|
||||
assert.equal(t.takeInstance(), 3);
|
||||
});
|
||||
|
||||
it('forEach', function() {
|
||||
let t = new TreapSet();
|
||||
t.insertRaw(2).insertRaw(3).insertRaw(-1).insertRaw(0).insertRaw(10);
|
||||
let a = [];
|
||||
t.forEach(x => a.push(x));
|
||||
assert.deepEqual(a, [-1, 0, 2, 3, 10])
|
||||
});
|
||||
});
|
34
xmake.lua
Normal file
34
xmake.lua
Normal file
@ -0,0 +1,34 @@
|
||||
set_project("arras")
|
||||
set_basename("arras")
|
||||
set_version("0.0a0")
|
||||
set_languages("c++17")
|
||||
set_targetdir(".")
|
||||
|
||||
add_requires("entt 3.11", "libsdl 2.26", "libsdl_image 2.6", "fmt 10")
|
||||
add_includedirs("include", ".")
|
||||
|
||||
|
||||
target("main")
|
||||
set_default(true)
|
||||
set_kind("binary")
|
||||
add_files("main.cpp")
|
||||
add_files("src/**/*.cpp")
|
||||
|
||||
add_packages("entt", "libsdl", "libsdl_image", "fmt")
|
||||
set_warnings("allextra")
|
||||
|
||||
target("test")
|
||||
set_default(false)
|
||||
set_kind("binary")
|
||||
set_prefixname("test-")
|
||||
|
||||
add_files("test/main.cpp")
|
||||
add_files("test/cases/*.cpp")
|
||||
add_files("src/**/*.cpp")
|
||||
|
||||
add_packages("entt", "fmt")
|
||||
|
||||
set_warnings("allextra")
|
||||
set_optimize("none")
|
||||
set_symbols("debug")
|
||||
add_defines("DEBUG")
|
Loading…
x
Reference in New Issue
Block a user