init dump

This commit is contained in:
a dinosaur 2024-05-05 17:01:56 +10:00
commit 608cf45822
53 changed files with 19224 additions and 0 deletions

11
.gitignore vendored Normal file
View File

@ -0,0 +1,11 @@
.DS_Store
/.build
/Packages
xcuserdata/
DerivedData/
.swiftpm/configuration/registries.json
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
.netrc
Package.resolved
*.blend1

41
Package.swift Normal file
View File

@ -0,0 +1,41 @@
// swift-tools-version: 5.9
import PackageDescription
let package = Package(
name: "CavesOfJolk-Swift",
platforms: [
.macOS(.v11)
],
products: [
.executable(name: "Test", targets: ["Test"]),
.library(name: "JolkEngine", targets: ["JolkEngine"])
],
dependencies: [
.package(
url: "https://github.com/ctreffs/SwiftSDL2.git",
.upToNextMajor(from: "1.4.1")),
.package(
url: "https://github.com/apple/swift-collections.git",
.upToNextMinor(from: "1.1.0"))
],
targets: [
.executableTarget(
name: "Test",
dependencies: [ .target(name: "JolkEngine") ],
resources: [ .process("Resources") ]
),
.target(
name: "JolkEngine",
dependencies: [
.product(name: "SDL", package: "SwiftSDL2"),
.product(name: "Collections", package: "swift-collections"),
"HSLuv"
],
swiftSettings: [
.unsafeFlags(["-Xcc", "-DGL_SILENCE_DEPRECATION"])
]
),
.target(name: "HSLuv"),
]
)

19
Sources/HSLuv/LICENSE.txt Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) 2015 Alexei Boronine
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

40
Sources/HSLuv/README.md Normal file
View File

@ -0,0 +1,40 @@
[![Cocoapod compatible](https://img.shields.io/cocoapods/v/hsluv-objc.svg)](https://cocoapods.org/pods/hsluv-objc)
[![Build Status](https://travis-ci.org/hsluv/hsluv-objc.svg?branch=master)](https://travis-ci.org/hsluv/hsluv-objc)
#hsluv-objc
Objective-C port of [HSLuv](http://www.hsluv.org).
##Which files are needed?
If you're using [CocoaPods](https://cocoapods.org) just add `pod 'hsluv-objc'` to your Podfile.
Otherwise, include this files in your project:
- hsluv-objc.h
- hsluv-objc+Tests.h
- hsluv-objc.c
##How to use
Import `hsluv-objc.h`, which defines the following functions:
~~~objective-c
// Accepts red, green and blue values between 0 and 1, returns the color in hex format, as in "#012C4A"
NSString *rgbToHex(CGFloat red, CGFloat green, CGFloat blue);
// Accepts an hex color, as in "#012C4A", and stores its red, green and blue components with values between 0 and 1.
BOOL hexToRgb(NSString *hex, CGFloat *red, CGFloat *green, CGFloat *blue);
// Hue is a value between 0 and 360, saturation and lightness between 0 and 100. Stores the RGB in values between 0 and 1.
void hsluvToRgb(CGFloat hue, CGFloat saturation, CGFloat lightness, CGFloat *red, CGFloat *green, CGFloat *blue);
// Red, green and blue values between 0 and 1, stores the hsluv components with hue between 0 and 360, saturation and lightness between 0 and 100.
void rgbToHsluv(CGFloat red, CGFloat green, CGFloat blue, CGFloat *hue, CGFloat *saturation, CGFloat *lightness);
// Hue is a value between 0 and 360, saturation and lightness between 0 and 100. Stores the RGB in values between 0 and 1.
void hpluvToRgb(CGFloat hue, CGFloat saturation, CGFloat lightness, CGFloat *red, CGFloat *green, CGFloat *blue);
// Red, green and blue values between 0 and 1, stores the hpluv components with hue between 0 and 360, saturation and lightness between 0 and 100.
void rgbToHpluv(CGFloat red, CGFloat green, CGFloat blue, CGFloat *hue, CGFloat *saturation, CGFloat *lightness);
~~~

View File

@ -0,0 +1,23 @@
//
// hsluv_objc+Test.h
// hsluv-objc
//
// Created by Roger Tallada on 4/6/15.
// Copyright (c) 2015 Alexei Boronine
//
#ifndef hsluv_objc_hsluv_objc_Test_h
#define hsluv_objc_hsluv_objc_Test_h
// Exposed for testing purposes only:
typedef struct tuple {
CGFloat a, b, c;
} Tuple;
Tuple rgbToXyz(Tuple rgb);
Tuple xyzToLuv(Tuple xyz);
Tuple luvToLch(Tuple luv);
Tuple lchToHsluv(Tuple lch);
Tuple lchToHpluv(Tuple lch);
#endif

510
Sources/HSLuv/hsluv-objc.m Normal file
View File

@ -0,0 +1,510 @@
//
// hsluv_objc.m
// hsluv-objc
//
// Created by Roger Tallada on 4/6/15.
// Copyright (c) 2015 Alexei Boronine
//
// Implementation of hsluv translated from hsluv.coffee
#import <tgmath.h>
#import "hsluv-objc.h"
#import "hsluv-objc+Test.h"
#pragma mark Private funcions
/*
# The math for most of this module was taken from:
#
# * http://www.easyrgb.com
# * http://www.brucelindbloom.com
# * Wikipedia
#
# All numbers taken from math/bounds.wxm wxMaxima file:
#
# fpprintprec: 16;
# CGFloat(M_XYZ_RGB);
# CGFloat(M_RGB_XYZ);
# CGFloat(refX);
# CGFloat(refY);
# CGFloat(refZ);
# CGFloat(refU);
# CGFloat(refV);
# CGFloat(lab_k);
# CGFloat(lab_e);
#*/
typedef struct tuple2 {
CGFloat a, b;
} Tuple2;
static Tuple m[3] = {
{ 3.2409699419045214, -1.5373831775700935, -0.49861076029300328}, // R
{-0.96924363628087983, 1.8759675015077207, 0.041555057407175613}, // G
{ 0.055630079696993609, -0.20397695888897657, 1.0569715142428786} // B
};
static Tuple m_inv[3] = {
{0.41239079926595948, 0.35758433938387796, 0.18048078840183429}, // X
{0.21263900587151036, 0.71516867876775593, 0.072192315360733715}, // Y
{0.019330818715591851, 0.11919477979462599, 0.95053215224966058} // Z
};
//Constants
CGFloat refU = 0.19783000664283681;
CGFloat refV = 0.468319994938791;
// CIE LUV constants
CGFloat kappa = 903.2962962962963;
CGFloat epsilon = 0.0088564516790356308;
// For a given lightness, return a list of 6 lines in slope-intercept
// form that represent the bounds in CIELUV, stepping over which will
// push a value out of the RGB gamut
Tuple2 * getBounds(CGFloat l) {
CGFloat sub1 = pow(l + 16, 3) / 1560896;
CGFloat sub2 = sub1 > epsilon ? sub1 : (l / kappa);
Tuple2 *ret = malloc(6 * sizeof(Tuple2));
for (int channel=0; channel<3; channel++) {
Tuple mTuple = m[channel];
CGFloat m1 = mTuple.a;
CGFloat m2 = mTuple.b;
CGFloat m3 = mTuple.c;
for (int t=0; t <= 1; t++) {
CGFloat top1 = (284517 * m1 - 94839 * m3) * sub2;
CGFloat top2 = (838422 * m3 + 769860 * m2 + 731718 * m1) * l * sub2 - 769860 * t * l;
CGFloat bottom = (632260 * m3 - 126452 * m2) * sub2 + 126452 * t;
Tuple2 tuple = {top1 / bottom, top2 / bottom};
NSUInteger lineNumber = channel * 2 + t;
ret[lineNumber] = tuple;
}
}
return ret;
}
CGFloat intersectLineLine(Tuple2 line1, Tuple2 line2) {
return (line1.b - line2.b) / (line2.a - line1.a);
}
CGFloat distanceFromPole(CGPoint point) {
return sqrt(pow(point.x, 2) + pow(point.y, 2));
}
CGFloat lengthOfRayUntilIntersect(CGFloat theta, Tuple2 line) {
// theta -- angle of ray starting at (0, 0)
// m, b -- slope and intercept of line
// x1, y1 -- coordinates of intersection
// len -- length of ray until it intersects with line
//
// b + m * x1 = y1
// len >= 0
// len * cos(theta) = x1
// len * sin(theta) = y1
//
//
// b + m * (len * cos(theta)) = len * sin(theta)
// b = len * sin(hrad) - m * len * cos(theta)
// b = len * (sin(hrad) - m * cos(hrad))
// len = b / (sin(hrad) - m * cos(hrad))
//
CGFloat m1 = line.a;
CGFloat b1 = line.b;
CGFloat len = b1 / (sin(theta) - m1 * cos(theta));
// if (len < 0) {
// return 0;
// }
return len;
}
// For given lightness, returns the maximum chroma. Keeping the chroma value
// below this number will ensure that for any hue, the color is within the RGB
// gamut.
CGFloat maxSafeChromaForL(CGFloat l) {
CGFloat minLength = CGFLOAT_MAX;
Tuple2 *bounds = getBounds(l);
for (NSUInteger i = 0; i < 6; i++) {
Tuple2 boundTuple = bounds[i];
CGFloat m1 = boundTuple.a;
CGFloat b1 = boundTuple.b;
// x where line intersects with perpendicular running though (0, 0)
Tuple2 line2 = {-1 / m1, 0};
CGFloat x = intersectLineLine(boundTuple, line2);
CGFloat distance = distanceFromPole(CGPointMake(x, b1 + x * m1));
if (distance >= 0) {
if (distance < minLength) {
minLength = distance;
}
}
}
free(bounds);
return minLength;
}
// For a given lightness and hue, return the maximum chroma that fits in
// the RGB gamut.
CGFloat maxChromaForLH(CGFloat l, CGFloat h) {
CGFloat hrad = h / 360 * M_PI * 2;
CGFloat minLength = CGFLOAT_MAX;
Tuple2 *bounds = getBounds(l);
for (NSUInteger i = 0; i < 6; i++) {
Tuple2 lineTuple = bounds[i];
CGFloat l = lengthOfRayUntilIntersect(hrad, lineTuple);
if (l >= 0) {
if (l < minLength) {
minLength = l;
}
}
}
free(bounds);
return minLength;
}
CGFloat tupleDotProduct(Tuple t1, Tuple t2) {
CGFloat ret = 0.0;
for (NSUInteger i = 0; i < 3; i++) {
switch (i) {
case 0:
ret += t1.a * t2.a;
break;
case 1:
ret += t1.b * t2.b;
break;
case 2:
ret += t1.c * t2.c;
break;
default:
break;
}
}
return ret;
}
// Used for rgb conversions
CGFloat fromLinear(CGFloat c) {
if (c <= 0.0031308) {
return 12.92 * c;
}
else {
return 1.055 * pow(c, 1 / 2.4) - 0.055;
}
}
CGFloat toLinear(CGFloat c) {
CGFloat a = 0.055;
if (c > 0.04045) {
return pow((c + a) / (1 + a), 2.4);
}
else {
return c / 12.92;
}
}
#pragma mark Conversion functions
Tuple xyzToRgb(Tuple xyz) {
CGFloat r = fromLinear(tupleDotProduct(m[0], xyz));
CGFloat g = fromLinear(tupleDotProduct(m[1], xyz));
CGFloat b = fromLinear(tupleDotProduct(m[2], xyz));
Tuple rgb = {r, g, b};
return rgb;
}
Tuple rgbToXyz(Tuple rgb) {
Tuple rgbl = {toLinear(rgb.a), toLinear(rgb.b), toLinear(rgb.c)};
CGFloat x = tupleDotProduct(m_inv[0], rgbl);
CGFloat y = tupleDotProduct(m_inv[1], rgbl);
CGFloat z = tupleDotProduct(m_inv[2], rgbl);
Tuple xyz = {x, y, z};
return xyz;
}
// http://en.wikipedia.org/wiki/CIELUV
// In these formulas, Yn refers to the reference white point. We are using
// illuminant D65, so Yn (see refY in Maxima file) equals 1. The formula is
// simplified accordingly.
CGFloat yToL (CGFloat y) {
CGFloat l;
if (y <= epsilon) {
l = y * kappa;
}
else {
l = 116.0 * pow(y, 1.0/3.0) - 16.0;
}
return l;
}
CGFloat lToY (CGFloat l) {
if (l <= 8) {
return l / kappa;
}
else {
return powl((l + 16) / 116, 3);
}
}
Tuple xyzToLuv(Tuple xyz) {
CGFloat varU = (4 * xyz.a) / (xyz.a + (15 * xyz.b) + (3 * xyz.c));
CGFloat varV = (9 * xyz.b) / (xyz.a + (15 * xyz.b) + (3 * xyz.c));
CGFloat l = yToL(xyz.b);
// Black will create a divide-by-zero error
if (l==0) {
Tuple luv = {0, 0, 0};
return luv;
}
CGFloat u = 13 * l * (varU - refU);
CGFloat v = 13 * l * (varV - refV);
Tuple luv = {l, u, v};
return luv;
}
Tuple luvToXyz(Tuple luv) {
// Black will create a divide-by-zero error
if (luv.a == 0) {
Tuple xyz = {0, 0, 0};
return xyz;
}
CGFloat varU = luv.b / (13 * luv.a) + refU;
CGFloat varV = luv.c / (13 * luv.a) + refV;
CGFloat y = lToY(luv.a);
CGFloat x = 0 - (9 * y * varU) / ((varU - 4) * varV - varU * varV);
CGFloat z = (9 * y - (15 * varV * y) - (varV * x)) / (3 * varV);
Tuple xyz = {x, y, z};
return xyz;
}
Tuple luvToLch(Tuple luv) {
CGFloat l = luv.a, u = luv.b, v = luv.c;
CGFloat h, c = sqrt(pow(u, 2) + pow(v, 2));
// Greys: disambiguate hue
if (c < 0.00000001) {
h = 0;
}
else {
CGFloat hrad = atan2(v, u);
h = hrad * 360 / 2 / M_PI;
if (h < 0) {
h = 360 + h;
}
}
Tuple lch = {l, c, h};
return lch;
}
Tuple lchToLuv(Tuple lch) {
CGFloat hRad = lch.c / 360 * 2 * M_PI;
CGFloat u = cos(hRad) * lch.b;
CGFloat v = sin(hRad) * lch.b;
Tuple luv = {lch.a, u, v};
return luv;
}
CGFloat checkBorders(CGFloat channel) {
if (channel < 0) {
return 0;
}
if (channel > 1) {
return 1;
}
return channel;
}
BOOL hexToInt(NSString *hex, unsigned int *result) {
NSScanner *scanner = [NSScanner scannerWithString:hex];
return [scanner scanHexInt:result];
}
#pragma mark hsluv
Tuple hsluvToLch(Tuple hsluv) {
CGFloat h = hsluv.a, s = hsluv.b, l = hsluv.c, c;
// White and black: disambiguate chroma
if (l > 99.9999999 || l < 0.00000001) {
c = 0;
}
else {
CGFloat max = maxChromaForLH(l, h);
c = max / 100 * s;
}
// Greys: disambiguate hue
if (s < 0.00000001) {
h = 0;
}
Tuple lch = {l, c, h};
return lch;
}
Tuple lchToHsluv(Tuple lch) {
CGFloat l = lch.a, c = lch.b, h = lch.c, s;
// White and black: disambiguate saturation
if (l > 99.9999999 || l < 0.00000001) {
s = 0;
}
else {
CGFloat max = maxChromaForLH(l, h);
s = c / max * 100;
}
// Greys: disambiguate hue
if (c < 0.00000001) {
h = 0;
}
Tuple hsluv = {h, s, l};
return hsluv;
}
#pragma mark hsluvP
Tuple hpluvToLch(Tuple hpluv) {
CGFloat h = hpluv.a, s = hpluv.b, l = hpluv.c, c;
// White and black: disambiguate chroma
if (l > 99.9999999 || l < 0.00000001) {
c = 0;
}
else {
CGFloat max = maxSafeChromaForL(l);
c = max / 100 * s;
}
// Greys: disambiguate hue
if (s < 0.00000001) {
h = 0;
}
Tuple lch = {l, c, h};
return lch;
}
Tuple lchToHpluv(Tuple lch) {
CGFloat l = lch.a, c = lch.b, h = lch.c, s;
// White and black: disambiguate saturation
if (l > 99.9999999 || l < 0.00000001) {
s = 0;
}
else {
CGFloat max = maxSafeChromaForL(l);
s = c / max * 100;
}
// Greys: disambiguate hue
if (c < 0.00000001) {
h = 0;
}
Tuple hpluv = {h, s, l};
return hpluv;
}
CGFloat roundTo6decimals(CGFloat channel) {
CGFloat ch = round(channel * 1e6) / 1e6;
if (ch < 0 || ch > 1) {
@throw [NSString stringWithFormat:@"Illegal rgb value: %@", @(ch)];
}
return ch;
}
#pragma mark Public functions
NSString *rgbToHex(CGFloat red, CGFloat green, CGFloat blue) {
NSString *hex = @"#";
CGFloat r = roundTo6decimals(red);
CGFloat g = roundTo6decimals(green);
CGFloat b = roundTo6decimals(blue);
NSString *R = [NSString stringWithFormat:@"%02X", (int)round(r * 255)];
NSString *G = [NSString stringWithFormat:@"%02X", (int)round(g * 255)];
NSString *B = [NSString stringWithFormat:@"%02X", (int)round(b * 255)];
return [[[hex stringByAppendingString:R] stringByAppendingString:G] stringByAppendingString:B];
}
BOOL hexToRgb(NSString *hex, CGFloat *red, CGFloat *green, CGFloat *blue) {
if ([hex length] >= 7) {
if ([hex characterAtIndex:0] == '#') {
hex = [hex substringFromIndex:1];
}
unsigned int r, g, b;
NSString *redS = [hex substringToIndex:2];
if (!hexToInt(redS, &r)) {
return NO;
}
NSRange gRange = {2, 2};
NSString *greenS = [hex substringWithRange:gRange];
if (!hexToInt(greenS, &g)) {
return NO;
}
NSRange bRange = {4, 2};
NSString *blueS = [hex substringWithRange:bRange];
if (!hexToInt(blueS, &b)) {
return NO;
}
*red = (CGFloat)r / 255;
*green = (CGFloat)g / 255;
*blue = (CGFloat)b / 255;
return YES;
}
return NO;
}
void hsluvToRgb(CGFloat hue, CGFloat saturation, CGFloat lightness, CGFloat *red, CGFloat *green, CGFloat *blue) {
Tuple hsluv = {hue, saturation, lightness};
Tuple rgb = xyzToRgb(luvToXyz(lchToLuv(hsluvToLch(hsluv))));
*red = rgb.a;
*green = rgb.b;
*blue = rgb.c;
}
void rgbToHsluv(CGFloat red, CGFloat green, CGFloat blue, CGFloat *hue, CGFloat *saturation, CGFloat *lightness) {
Tuple rgb = {red, green, blue};
Tuple hsluv = lchToHsluv(luvToLch(xyzToLuv(rgbToXyz(rgb))));
*hue = hsluv.a;
*saturation = hsluv.b;
*lightness = hsluv.c;
}
void hpluvToRgb(CGFloat hue, CGFloat saturation, CGFloat lightness, CGFloat *red, CGFloat *green, CGFloat *blue) {
Tuple hpluv = {hue, saturation, lightness};
Tuple rgb = xyzToRgb(luvToXyz(lchToLuv(hpluvToLch(hpluv))));
*red = rgb.a;
*green = rgb.b;
*blue = rgb.c;
}
void rgbToHpluv(CGFloat red, CGFloat green, CGFloat blue, CGFloat *hue, CGFloat *saturation, CGFloat *lightness) {
Tuple rgb = {red, green, blue};
Tuple hpluv = lchToHpluv(luvToLch(xyzToLuv(rgbToXyz(rgb))));
*hue = hpluv.a;
*saturation = hpluv.b;
*lightness = hpluv.c;
}

View File

@ -0,0 +1,33 @@
//
// hsluv_objc.h
// hsluv-objc
//
// Created by Roger Tallada on 4/6/15.
// Copyright (c) 2015 Alexei Boronine
//
#import <Foundation/Foundation.h>
#import <CoreGraphics/CoreGraphics.h>
#ifndef hsluv_objc_h
#define hsluv_objc_h
// Accepts red, green and blue values between 0 and 1, returns the color in hex format, as in "#012C4A"
NSString *rgbToHex(CGFloat red, CGFloat green, CGFloat blue);
// Accepts an hex color, as in "#012C4A", and stores its red, green and blue components with values between 0 and 1.
BOOL hexToRgb(NSString *hex, CGFloat *red, CGFloat *green, CGFloat *blue);
// Hue is a value between 0 and 360, saturation and lightness between 0 and 100. Stores the RGB in values between 0 and 1.
void hsluvToRgb(CGFloat hue, CGFloat saturation, CGFloat lightness, CGFloat *red, CGFloat *green, CGFloat *blue);
// Red, green and blue values between 0 and 1, stores the hsluv components with hue between 0 and 360, saturation and lightness between 0 and 100.
void rgbToHsluv(CGFloat red, CGFloat green, CGFloat blue, CGFloat *hue, CGFloat *saturation, CGFloat *lightness);
// Hue is a value between 0 and 360, saturation and lightness between 0 and 100. Stores the RGB in values between 0 and 1.
void hpluvToRgb(CGFloat hue, CGFloat saturation, CGFloat lightness, CGFloat *red, CGFloat *green, CGFloat *blue);
// Red, green and blue values between 0 and 1, stores the hpluv components with hue between 0 and 360, saturation and lightness between 0 and 100.
void rgbToHpluv(CGFloat red, CGFloat green, CGFloat blue, CGFloat *hue, CGFloat *saturation, CGFloat *lightness);
#endif

View File

@ -0,0 +1,205 @@
import Foundation
import SDL
public protocol ApplicationImplementation
{
func create(render: inout Renderer)
func quit()
func loadContent(content: inout ContentManager) throws
func resize(width: Int, height: Int)
func update(deltaTime: Float)
func draw(render: inout Renderer, deltaTime: Float)
}
extension ApplicationImplementation
{
public func quit() {}
}
public struct ApplicationConfiguration
{
let resizable: Bool
let vSync: VSyncMode
let windowWidth: Int32
let windowHeight: Int32
let bundle: Bundle
public init(resizable: Bool, vSync: VSyncMode, windowWidth: Int32, windowHeight: Int32, bundle: Bundle)
{
self.resizable = resizable
self.vSync = vSync
self.windowWidth = windowWidth
self.windowHeight = windowHeight
self.bundle = bundle
}
}
//FIXME: Wrap these
public var sdlPad: OpaquePointer? = nil
var joyId: Int32 = -1
public class Application
{
private let implementation: ApplicationImplementation
private let configuration: ApplicationConfiguration
private var window: OpaquePointer?
private var render: Renderer
private var content: ContentManager
typealias ResizeEvent = (width: Int32, height: Int32)
private var resize: ResizeEvent? = nil
func openController(index: Int32) -> (pad: OpaquePointer, joy: Int32)?
{
guard let sdlPad = SDL_GameControllerOpen(index) else { return nil }
let joyId = SDL_JoystickGetDeviceInstanceID(index)
print("Using gamepad #\(joyId), \"\(String(cString: SDL_GameControllerName(sdlPad)))\"")
return (pad: sdlPad, joy: joyId)
}
public init(application: ApplicationImplementation, config: ApplicationConfiguration)
{
self.implementation = application
self.configuration = config
self.render = OpenGL(version: .init(major: 1, minor: 5))
self.content = ContentManager(&self.render, bundle: configuration.bundle)
}
public func run()
{
guard SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER) == 0
else { fatalError("SDL_Init: \(String(cString: SDL_GetError()))") }
let winPos = Int32(SDL_WINDOWPOS_UNDEFINED_MASK)
let winWidth: Int32 = configuration.windowWidth, winHeight: Int32 = configuration.windowHeight
var winFlags = SDL_WINDOW_RESIZABLE.rawValue | SDL_WINDOW_ALLOW_HIGHDPI.rawValue
if render is OpenGL { winFlags |= SDL_WINDOW_OPENGL.rawValue }
window = SDL_CreateWindow("Something", winPos, winPos, winWidth, winHeight, winFlags)
guard window != nil else { fatalError("SDL_CreateWindow: \(String(cString: SDL_GetError()))") }
do
{
try render.create(sdlWindow: window!)
try render.setVsync(mode: configuration.vSync)
}
catch RendererError.sdlError(let message)
{
fatalError("idk error \(message)")
}
catch { fatalError("piss") }
for i in 0...SDL_NumJoysticks()
{
if SDL_IsGameController(i) != SDL_TRUE { continue }
if let open = openController(index: i)
{
sdlPad = open.pad
joyId = open.joy
break
}
}
do
{
implementation.create(render: &render)
content.setLoader(extension: "obj", loader: ObjLoader())
content.setLoader(extension: "g3db", loader: G3DbLoader())
content.setLoader(extensions: ["png", "jpg"], loader: NSImageLoader())
try implementation.loadContent(content: &content)
}
catch
{
fatalError("loadContent()")
}
let timeMul = 1 / Double(SDL_GetPerformanceFrequency());
var prevTime = SDL_GetPerformanceCounter();
resize = displaySize
mainLoop: while true
{
Input.instance.newTick()
var event = SDL_Event()
while SDL_PollEvent(&event) > 0
{
switch SDL_EventType(event.type)
{
case SDL_QUIT: break mainLoop
case SDL_KEYDOWN:
if event.key.repeat == 0
{
Input.instance.pressEvent(scan: event.key.keysym.scancode)
}
case SDL_KEYUP:
Input.instance.releaseEvent(scan: event.key.keysym.scancode)
case SDL_WINDOWEVENT:
switch SDL_WindowEventID(UInt32(event.window.event))
{
case SDL_WINDOWEVENT_RESIZED:
resize = displaySize
default: break
}
case SDL_CONTROLLERDEVICEADDED:
if sdlPad == nil && SDL_IsGameController(event.cdevice.which) == SDL_TRUE
{
if let open = openController(index: event.cdevice.which)
{
sdlPad = open.pad
joyId = open.joy
}
}
case SDL_CONTROLLERDEVICEREMOVED:
if event.cdevice.which == joyId
{
SDL_GameControllerClose(sdlPad)
sdlPad = nil
joyId = -1
}
default: break
}
}
let time = SDL_GetPerformanceCounter();
let deltaTime = Float(Double(time - prevTime) * timeMul)
prevTime = time
implementation.update(deltaTime: min(deltaTime, 1.0 / 15.0))
repaint(deltaTime)
}
implementation.quit()
content.releaseAll()
render.delete()
SDL_DestroyWindow(window)
SDL_Quit()
}
private func repaint(_ deltaTime: Float)
{
if resize != nil
{
render.resize(width: resize!.width, height: resize!.height)
implementation.resize(width: Int(resize!.width), height: Int(resize!.height))
resize = nil
}
render.newFrame()
implementation.draw(render: &render, deltaTime: deltaTime)
SDL_GL_SwapWindow(window)
}
private var displaySize: ResizeEvent
{
var width: Int32 = 0, height: Int32 = 0
SDL_GL_GetDrawableSize(window, &width, &height)
return (width, height)
}
}

View File

@ -0,0 +1,95 @@
import SDL2
import HSLuv
public struct Colour: Equatable { let r, g, b, a: Float }
public extension Colour
{
static var zero: Self { .init(r: 0, g: 0, b: 0, a: 0) }
static var black: Self { .init(r: 0, g: 0, b: 0, a: 1) }
static var white: Self { .init(r: 1, g: 1, b: 1, a: 1) }
init(r: Float, g: Float, b: Float)
{
self.init(r: r, g: g, b: b, a: 1.0)
}
init(fromSdl: SDL_Color)
{
self.init(
r: Float(fromSdl.r) / 0xFF,
g: Float(fromSdl.g) / 0xFF,
b: Float(fromSdl.b) / 0xFF,
a: Float(fromSdl.a) / 0xFF)
}
init(fromRgb24: UInt32)
{
self.init(
r: Float((fromRgb24 >> 16) & 0xFF) / 0xFF,
g: Float((fromRgb24 >> 8) & 0xFF) / 0xFF,
b: Float((fromRgb24 >> 0) & 0xFF) / 0xFF,
a: 1)
}
init(fromRgba32: UInt32)
{
self.init(
r: Float((fromRgba32 >> 24) & 0xFF) / 0xFF,
g: Float((fromRgba32 >> 16) & 0xFF) / 0xFF,
b: Float((fromRgba32 >> 8) & 0xFF) / 0xFF,
a: Float((fromRgba32 >> 0) & 0xFF) / 0xFF)
}
func mix(with rhs: Colour, _ amount: Float) -> Colour
{
let x = amount.saturate
return Colour(
r: Float.lerp(r, rhs.r, x),
g: Float.lerp(g, rhs.g, x),
b: Float.lerp(b, rhs.b, x),
a: Float.lerp(a, rhs.a, x))
}
func lighten(by: Double) -> Colour
{
var hue = CGFloat(), sat = CGFloat(), light = CGFloat()
rgbToHsluv(CGFloat(r), CGFloat(g), CGFloat(b), &hue, &sat, &light)
let gamma: CGFloat = 3.0
light = pow((pow(light / 100, 1 / gamma) + CGFloat(by) * 0.8).saturate, gamma) * 100
var newR = CGFloat(), newG = CGFloat(), newB = CGFloat()
hsluvToRgb(hue, sat, light, &newR, &newG, &newB)
return Colour(r: Float(newR), g: Float(newG), b: Float(newB), a: a)
}
var linear: Colour { get
{
Colour(
r: linearFromSrgb(r),
g: linearFromSrgb(g),
b: linearFromSrgb(b),
a: a)
} }
func setAlpha(_ newAlpha: Float) -> Colour
{
return Colour(r: r, g: g, b: b, a: newAlpha)
}
}
@inline(__always) fileprivate func srgbFromLinear(_ x: Float) -> Float
{
return (x < 0.0031308)
? x * 12.92
: 1.055 * pow(x, 1.0 / 2.4) - 0.055
}
@inline(__always) fileprivate func linearFromSrgb(_ x: Float) -> Float
{
return (x < 0.04045)
? x / 12.92
: pow((x + 0.055) / 1.055, 2.4)
}

View File

@ -0,0 +1,165 @@
import Foundation
public protocol ContentLoaderParametersProtocol
{
associatedtype T: Resource
}
public struct ContentManager
{
private var loaders = Dictionary<String, any LoaderProtocol>()
private var resources = [any RendererResource]()
private let renderer: Renderer
private let bundle: Bundle
}
extension ContentManager
{
internal init(_ renderer: inout Renderer, bundle: Bundle)
{
self.renderer = renderer
self.bundle = bundle
}
public mutating func setLoader<T: Resource, Loader: LoaderProtocol>(extensions exts: [String], loader: Loader) where Loader.T == T
{
for i in exts
{
setLoader(extension: i, loader: loader)
}
}
public mutating func setLoader<T: Resource, Loader: LoaderProtocol>(extension ext: String, loader: Loader) where Loader.T == T
{
loaders[ext] = loader
}
public mutating func create(mesh: Mesh) throws -> RenderMesh
{
let rendMesh = try renderer.createMesh(mesh: mesh)
resources.append(rendMesh)
return rendMesh
}
public mutating func create(texture: Image, params: Texture2DParameters = .init()) throws -> Texture2D
{
try texture.pixels.withUnsafeBytes
{
raw in
let data = raw.baseAddress!
let rendTex = try renderer.createTexture(
data: data, width: texture.width, height: texture.height,
filter: params.magFilter, mipMode: params.mipMode)
resources.append(rendTex)
return Texture2D(id: rendTex, width: texture.width, height: texture.height)
}
}
public func getResource(_ path: String) throws -> URL
{
guard let extIndex = path.lastIndex(of: ".")
else { throw ContentError.badPath }
let name = String(path[..<extIndex]), ext = String(path[extIndex...])
guard let resourceUrl: URL = bundle.url(
forResource: name,
withExtension: ext)
else { throw ContentError.resourceNotFound }
return resourceUrl
}
public mutating func load<T: Resource>(_ path: String) throws -> T
{
let resourceUrl = try getResource(path)
let loader = loaders[resourceUrl.pathExtension]
guard let resource = loader?.load(url: resourceUrl)
else { throw ContentError.loadFailure }
if T.self == Mesh.self
{
guard let mesh = resource as? Mesh else { throw ContentError.loadFailure }
return mesh as! T
}
if T.self == Image.self
{
guard let image = resource as? Image else { throw ContentError.loadFailure }
return image as! T
}
if T.self == Texture2D.self
{
guard let image = resource as? Image else { throw ContentError.loadFailure }
let texture2D = try self.create(texture: image)
return texture2D as! T
}
throw ContentError.loadFailure
}
public mutating func load<T: Resource, Params: ContentLoaderParametersProtocol>(_ path: String, params: Params) throws
-> T where Params.T == T
{
let resourceUrl = try getResource(path)
let loader = loaders[resourceUrl.pathExtension]
guard let resource = loader?.load(url: resourceUrl)
else { throw ContentError.loadFailure }
if T.self == Mesh.self
{
guard let mesh = resource as? Mesh else { throw ContentError.loadFailure }
return mesh as! T
}
if T.self == Image.self
{
guard let image = resource as? Image else { throw ContentError.loadFailure }
return image as! T
}
if T.self == Texture2D.self
{
guard let image = resource as? Image else { throw ContentError.loadFailure }
let texture2D = try self.create(texture: image, params: params as! Texture2DParameters)
return texture2D as! T
}
throw ContentError.loadFailure
}
public mutating func load<T: RendererResource>(_ path: String) throws -> T
{
let resourceUrl = try getResource(path)
let loader = loaders[resourceUrl.pathExtension]
guard let resource = loader?.load(url: resourceUrl)
else { throw ContentError.loadFailure }
if T.self == RenderMesh.self
{
guard let mesh = resource as? Mesh else { throw ContentError.loadFailure }
let renderResource = try self.create(mesh: mesh)
return renderResource as! T
}
throw ContentError.loadFailure
}
internal mutating func releaseAll()
{
for resource in resources.reversed()
{
if resource is RenderMesh
{
renderer.deleteMesh(resource as! RenderMesh)
}
else if resource is RenderTexture2D
{
renderer.deleteTexture(resource as! RenderTexture2D)
}
}
resources.removeAll()
}
}
public enum ContentError: Error
{
case badPath //else { fatalError("Malformed resource path \"\(path)\"") }
case resourceNotFound //else { fatalError("Resource \"\(path)\" doesn't exist") }
case loadFailure //else { fatalError("Failed to load resource \"\(path)\"") }
}

View File

@ -0,0 +1,9 @@
import Foundation
public struct Image: Resource
{
let pixels: Data
let width: Int
let height: Int
}

View File

@ -0,0 +1,9 @@
import Foundation
public protocol LoaderProtocol
{
associatedtype T: Resource
func load(url: URL) -> T?
}

View File

@ -0,0 +1,54 @@
import OrderedCollections
public struct Model: Resource
{
public let meshes: [Mesh]
}
public struct Mesh: Resource
{
public typealias Index = UInt16
public struct Vertex: Equatable
{
public let position: Vec3f
public let normal: Vec3f
public let texCoord: Vec2f
public init(position: Vec3f, normal: Vec3f, texCoord: Vec2f)
{
self.position = position
self.normal = normal
self.texCoord = texCoord
}
}
public struct SubMesh
{
public let start, length: Int
public init(start: Int, length: Int)
{
self.start = start
self.length = length
}
}
public let vertices: [Vertex]
public let indices: [Index]
public let subMeshes: OrderedDictionary<String, SubMesh>
}
public extension Mesh
{
static let empty = Self(vertices: .init(), indices: .init(), subMeshes: .init())
init(vertices: [Vertex], indices: [Index])
{
self.init(
vertices: vertices,
indices: indices,
subMeshes: .init())
}
}

View File

@ -0,0 +1,120 @@
import Foundation
public struct ObjModel
{
public var positions = [Vec3f]()
public var normals = [Vec3f]()
public var texCoords = [Vec2f]()
public var objects = Dictionary<String, Object>()
public struct Object { public var faces = [Face]() }
public struct Index { public let p: Int, n: Int, t: Int }
public enum Face
{
case line(p1: Int, p2: Int)
case triangle(v1: Index, v2: Index, v3: Index)
case quad(v1: Index, v2: Index, v3: Index, v4: Index)
case ngon(_ v: [Index])
}
}
public struct ObjMaterial
{
var model: IlluminationModel = .lambert
var ambient: Colour = .black
var diffuse: Colour = .white
var specular: Colour = .zero
var specularExp: Float = 30
var refraction: Float = 1.0
var alpha: Float = 1.0
var ambientMap: TextureMap? = nil
var diffuseMap: TextureMap? = nil
var specularMap: TextureMap? = nil
var specularExpMap: TextureMap? = nil
var dissolveMap: TextureMap? = nil
var bumpMap: TextureMap? = nil
var displacementMap: TextureMap? = nil
var decalMap: TextureMap? = nil
// PBR extensions
var roughness: Float = 0.5
var metallic: Float = 0.0
var sheen: Float = 0.0
var clearcoatThickness: Float = 0.0
var clearcoatRoughness: Float = 0.03 // dunno what the default should be so here's the blender one
var emmision: Vec3f = .zero
var anisotropy: Float = 0.0
var anisotropyRotation: Float = 0.0
var roughnessMap: TextureMap? = nil
var metallicMap: TextureMap? = nil
var sheenMap: TextureMap? = nil
var emmisionMap: TextureMap? = nil
var normalMap: TextureMap? = nil
// Ft Fresnel reflectance
// Ft Fresnel transmittance
// Ns Specular exponent/shininess
// Ia Ambient light
// I Light intensity
// Ir Intensity from reflected direction (reflection map and/or raytracing)
// It Intensity from transmitted direction
// Ka Ambient reflectance
// Kd Diffuse reflectance
// Ks Specular reflectance
// Tf Transmission filter
// H Unit vector bisector between L and V
// L Unit light vector
// N Unit surface normal
// V Unit view vector
//
// j = piss
// Fr = Fresnel
// lambert = KaIa + Kd(N * Lj)Ij
// blinnPhong = lambert + Ks((H * Hj) ^ Ns)Ij
//
// Notes: Clara.io only usees 0 and 1, maya always exports 4, blender can export 1, 3, 6
public enum IlluminationModel
{
case colour // Kd only
case lambert
case blinnPhong
case reflectionRaytrace // + Ir = (intensity of reflection map) + (ray trace)
case transparentGlassReflectionRaytrace // + Ir = (glass) + (intensity of reflection map) + (raytrace)
case reflectionRaytraceFresnel // Fr(Lj * Hj, Ks, Ns)Ij + Fr(N * V, Ks, Ns)Ir
case transparentRefractionReflectionRaytrace // + Ir + (1 - Ks)TfIt
case transparentRefractionReflectionRaytraceFresnel // Fr(Lj * Hj, Ks, Ns)Ij + Fr(N * V, Ks, Ns)Ir
case reflection // + Ir = (intensity of reflection map)
case transparentGlassReflection // + Ir = (glass) + (intensity of reflection map) + (raytrace)
case shadowOnly
}
public struct TextureMap
{
var path: String = .init()
var flags: Flags = [ .blendHoriz, .blendVert ]
var blendMul: Float = 1.0
var boost: Float = 0.0 // non-negative
var imfChan: ImfChan = .l // decal ? .m : .l
var mmBaseGain: (Float, Float) = (0.0, 1.0)
var offset: Vec3f = .zero
var scale: Vec3f = .one
var turbulence: Vec3f = .zero
var textureResolution: Int = 0
struct Flags: OptionSet
{
let rawValue: UInt8
static let blendHoriz: Self = Self(rawValue: 1 << 0)
static let blendVert: Self = Self(rawValue: 1 << 1)
static let colourCorrection: Self = Self(rawValue: 1 << 2)
static let clamp: Self = Self(rawValue: 1 << 3)
}
enum ImfChan { case r, g, b, m, l, z }
}
}

View File

@ -0,0 +1,4 @@
public protocol Resource
{
}

View File

@ -0,0 +1,155 @@
import SDL2
public class Input
{
private static let _instance = Input()
public static var instance: Input { _instance }
//private let _UP: UInt8 = 0
//private let _PRESS: UInt8 = 1
//private let _DOWN: UInt8 = 2
//private let _RELEASE: UInt8 = 3
private static let _UP: UInt8 = 0b000
private static let _DOWN: UInt8 = 0b010
private static let _IMPULSE: UInt8 = 0b001
private static let _REPEAT: UInt8 = 0b100
private static let _PRESS: UInt8 = _DOWN | _IMPULSE
private static let _RELEASE: UInt8 = _UP | _IMPULSE
private var _keys = [UInt8](repeating: _UP, count: Int(SDL_NUM_SCANCODES.rawValue))
private init() {}
internal func newTick()
{
//_keys = _keys.map({ i in (i & 0x1) == 0x1 ? ((i &+ 1) & 0x3) : i })
_keys = _keys.map({ $0 & ~Input._IMPULSE })
//for i in 0..<Int(SDL_NUM_SCANCODES.rawValue)
//{
// _keys[i] &= ~0x1
//}
}
internal func pressEvent(scan: SDL_Scancode)
{
_keys[Int(scan.rawValue)] = Input._PRESS
}
internal func releaseEvent(scan: SDL_Scancode)
{
_keys[Int(scan.rawValue)] = Input._RELEASE
}
public func keyDown(_ key: Key) -> Bool
{
//SDL_GetKeyboardState(nil).withMemoryRebound(to: UInt8.self, capacity: Int(SDL_NUM_SCANCODES.rawValue))
//{ state in
// state[Int(key.sdlScancode.rawValue)] != 0
//}
_keys[Int(key.sdlScancode.rawValue)] & Input._DOWN == Input._DOWN
}
public func keyPressed(_ key: Key) -> Bool
{
_keys[Int(key.sdlScancode.rawValue)] == Input._PRESS
}
public func keyReleased(_ key: Key) -> Bool
{
_keys[Int(key.sdlKeycode.rawValue)] == Input._RELEASE
}
}
public enum Key
{
case a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z
case right, left, down, up
}
extension Key
{
internal var sdlKeycode: SDL_KeyCode
{
switch self
{
case .a: SDLK_a
case .b: SDLK_a
case .c: SDLK_a
case .d: SDLK_a
case .e: SDLK_a
case .f: SDLK_a
case .g: SDLK_a
case .h: SDLK_a
case .i: SDLK_a
case .j: SDLK_a
case .k: SDLK_a
case .l: SDLK_a
case .m: SDLK_a
case .n: SDLK_a
case .o: SDLK_a
case .p: SDLK_a
case .q: SDLK_a
case .r: SDLK_a
case .s: SDLK_a
case .t: SDLK_a
case .u: SDLK_a
case .v: SDLK_a
case .w: SDLK_a
case .x: SDLK_a
case .y: SDLK_a
case .z: SDLK_a
case .left: SDLK_LEFT
case .right: SDLK_RIGHT
case .up: SDLK_UP
case .down: SDLK_DOWN
}
}
internal var sdlScancode: SDL_Scancode
{
switch self
{
case .a: SDL_SCANCODE_A
case .b: SDL_SCANCODE_B
case .c: SDL_SCANCODE_C
case .d: SDL_SCANCODE_D
case .e: SDL_SCANCODE_E
case .f: SDL_SCANCODE_F
case .g: SDL_SCANCODE_G
case .h: SDL_SCANCODE_H
case .i: SDL_SCANCODE_I
case .j: SDL_SCANCODE_J
case .k: SDL_SCANCODE_K
case .l: SDL_SCANCODE_L
case .m: SDL_SCANCODE_M
case .n: SDL_SCANCODE_N
case .o: SDL_SCANCODE_O
case .p: SDL_SCANCODE_P
case .q: SDL_SCANCODE_Q
case .r: SDL_SCANCODE_R
case .s: SDL_SCANCODE_S
case .t: SDL_SCANCODE_T
case .u: SDL_SCANCODE_U
case .v: SDL_SCANCODE_V
case .w: SDL_SCANCODE_W
case .x: SDL_SCANCODE_X
case .y: SDL_SCANCODE_Y
case .z: SDL_SCANCODE_Z
case .left: SDL_SCANCODE_LEFT
case .right: SDL_SCANCODE_RIGHT
case .up: SDL_SCANCODE_UP
case .down: SDL_SCANCODE_DOWN
}
}
}
extension SDL_KeyCode
{
internal init(scancode: SDL_Scancode)
{
self.init(scancode.rawValue | UInt32(SDLK_SCANCODE_MASK))
}
}

View File

@ -0,0 +1,249 @@
import Foundation
import OrderedCollections
class G3DbLoader: LoaderProtocol
{
typealias T = Mesh
func load(url: URL) -> T?
{
guard var reader = try? G3DbReader(url)
else { return Optional.none }
return try? reader.read()
}
}
fileprivate struct G3DbReader
{
private let version: [Int16] = [0, 1]
private var reader: UBJsonReader
init(_ url: URL) throws
{
guard let file = try? FileHandle(forReadingFrom: url)
else { throw G3DbReaderError.fileNotFound }
self.reader = UBJsonReader(file: file)
}
mutating func read() throws -> Mesh
{
let model = try readModel()
let mesh = model.meshes[0]
var vertices = [Mesh.Vertex]()
var indices = [UInt16]()
var subMeshes = OrderedDictionary<String, Mesh.SubMesh>()
let attributeSize =
{ (attrib: G3DAttribute) in
switch attrib
{
case .position, .normal, .colour, .tangent, .bitangent:
return 3
case .texCoord(_), .blendWeight(_):
return 2
case .colourPacked:
return 1
}
}
var stride = 0
for a in mesh.attributes
{
stride += attributeSize(a)
}
let numVerts = mesh.vertices.count / stride
vertices.reserveCapacity(numVerts)
for i in 0..<numVerts
{
var srcIdx = i * stride
var position: Vec3f = .zero, normal: Vec3f = .zero
var texCoord: Vec2f = .zero
for a in mesh.attributes
{
switch a
{
case .position:
position = .init(
mesh.vertices[srcIdx],
mesh.vertices[srcIdx + 2],
-mesh.vertices[srcIdx + 1])
case .normal:
normal = .init(
mesh.vertices[srcIdx],
mesh.vertices[srcIdx + 2],
-mesh.vertices[srcIdx + 1])
case .texCoord(id: 0):
texCoord = .init(
mesh.vertices[srcIdx],
1.0 - mesh.vertices[srcIdx + 1])
default: break
}
srcIdx += attributeSize(a)
}
vertices.append(.init(position: position, normal: normal, texCoord: texCoord))
}
for part in mesh.parts
{
subMeshes[part.key] = .init(start: indices.count, length: part.value.indices.count)
indices += part.value.indices.map { UInt16($0) }
}
return Mesh(vertices: vertices, indices: indices, subMeshes: subMeshes)
}
mutating func readModel() throws -> G3DModel
{
let root = try reader.read()
let version = try root.getArray(key: "version")
guard try version.count == 2 && (try version.map({ try $0.int16 }) == self.version)
else { throw G3DbReaderError.versionMismatch }
var model = G3DModel()
model.id = try root.getString(key: "id", default: "")
try readMeshes(root, &model)
try readMaterials(root, &model)
return model
}
mutating func readMeshes(_ root: UBJsonToken, _ model: inout G3DModel) throws
{
let meshes = try root.getArray(key: "meshes")
for obj in meshes
{
var mesh = G3DModelMesh()
mesh.id = try obj.getString(key: "id", default: "")
for attrib in try obj.getArray(key: "attributes")
{
mesh.attributes.append(try .resolve(try attrib.string))
}
mesh.vertices = try obj.getFloatArray(key: "vertices")
for partObj in try obj.getArray(key: "parts")
{
let id = try partObj.getString(key: "id")
if mesh.parts.keys.contains(id) { throw G3DbReaderError.duplicateIDs }
var part = G3dModelMeshPart()
part.mode = try .resolve(try partObj.getString(key: "type"))
part.indices = try partObj.getInt16Array(key: "indices")
mesh.parts[id] = part
}
model.meshes.append(mesh)
}
}
mutating func readMaterials(_ root: UBJsonToken, _ model: inout G3DModel) throws
{
}
}
fileprivate struct G3DModel
{
var id: String = .init()
var meshes: [G3DModelMesh] = .init()
}
fileprivate struct G3DModelMesh
{
var id: String = .init()
var attributes: [G3DAttribute] = .init()
var vertices: [Float] = .init()
var parts: Dictionary<String, G3dModelMeshPart> = .init()
}
fileprivate struct G3dModelMeshPart
{
var mode: G3DPrimativeType = .invalid
var indices: [Int16] = .init()
}
fileprivate enum G3DAttribute: Equatable
{
case position
case normal
case tangent
case bitangent
case colour
case colourPacked
case texCoord(id: UInt8)
case blendWeight(id: UInt8)
static let order = [ .position, .colour, .colourPacked, .normal,
texCoord(id: 0), .blendWeight(id: 0), .tangent, .bitangent ]
}
extension G3DAttribute
{
static func resolve(_ attrib: String) throws -> Self
{
let getAttributeId =
{ (attrib: String, offset: Int) throws -> UInt8 in
let idIdx = attrib.index(attrib.startIndex, offsetBy: offset)
let idStr = attrib.suffix(from: idIdx)
guard let id = UInt8(idStr)
else { throw G3DbReaderError.badAttribute }
return id
}
return switch attrib
{
case "POSITION": .position
case "NORMAL": .normal
case "COLOR": .colour
case "COLORPACKED": .colourPacked
case "TANGENT": .tangent
case "BINORMAL": .bitangent
default:
if attrib.starts(with: "TEXCOORD")
{
.texCoord(id: try getAttributeId(attrib, 8))
}
else if attrib.starts(with: "BLENDWEIGHT")
{
.blendWeight(id: try getAttributeId(attrib, 11))
}
else { throw G3DbReaderError.badAttribute }
}
}
}
fileprivate enum G3DPrimativeType
{
case invalid
case triangles
case lines
case points
case triangleStrip
case lineStrip
}
extension G3DPrimativeType
{
static func resolve(_ key: String) throws -> Self
{
switch key
{
case "TRIANGLES": .triangles
case "LINES": .lines
case "POINTS": .points
case "TRIANGLE_STRIP": .triangleStrip
case "LINE_STRIP": .lineStrip
default: throw G3DbReaderError.invalidFormat
}
}
}
fileprivate enum G3DbReaderError: Error
{
case fileNotFound
case versionMismatch
case unsupportedFormat
case invalidFormat
case badAttribute
case duplicateIDs
}

View File

@ -0,0 +1,50 @@
import AppKit
enum ImageLoaderError: Error
{
case loadFailed(_ message: String)
}
struct NSImageLoader: LoaderProtocol
{
typealias T = Image
func load(url: URL) -> T?
{
guard let image = try? NSImageLoader.loadImage(url: url) else { return nil }
return image
}
static func loadImage(url: URL) throws -> Image
{
try autoreleasepool
{
// Open as a Core Graphics image
guard let nsImg = NSImage(contentsOf: url),
let image = nsImg.cgImage(forProposedRect: nil, context: nil, hints: nil)
else { throw ImageLoaderError.loadFailed("Failed to open image \"\(url.absoluteString)\"") }
// Convert to 8-bit ARGB (SRGB) w/ premultiplied alpha
let alphaInfo = image.alphaInfo == .none ? CGImageAlphaInfo.noneSkipLast : CGImageAlphaInfo.premultipliedLast
guard let colourspace = CGColorSpace(name: CGColorSpace.sRGB),
let context = CGContext(data: nil,
width: image.width,
height: image.height,
bitsPerComponent: 8,
bytesPerRow: image.width * 4,
space: colourspace,
bitmapInfo: alphaInfo.rawValue | CGBitmapInfo.byteOrder32Big.rawValue)
else { throw ImageLoaderError.loadFailed("Coudn't create graphics context") }
let flipVertical = CGAffineTransform(a: 1, b: 0, c: 0, d: -1, tx: 0, ty: CGFloat(image.height))
context.concatenate(flipVertical)
context.draw(image, in: CGRect(x: 0, y: 0, width: image.width, height: image.height))
guard let data = context.data else { throw ImageLoaderError.loadFailed("what") }
return Image(
pixels: Data(bytes: data, count: 4 * image.width * image.height),
width: image.width,
height: image.height)
}
}
}

View File

@ -0,0 +1,92 @@
import Foundation
import OrderedCollections
/*
extension ObjMaterial
{
func convert() -> Material
{
var m = Material()
m.diffuse = self.diffuse.setAlpha(self.alpha)
if ![ .colour, .lambert, .shadowOnly ].contains(self.model)
{
m.specular = self.specular
m.specularExp = self.specularExp
}
return m
}
}
*/
public struct ObjLoader: LoaderProtocol
{
public typealias T = Mesh
public init() {}
public func load(url: URL) -> T?
{
try? Self.read(url: url)
}
private static func read(url: URL) throws -> Mesh
{
return try read(model: try ObjReader.read(url: url))
}
public static func read(model: ObjModel) throws -> Mesh
{
var subMeshes: OrderedDictionary<String, Mesh.SubMesh> = .init()
var vertices = [Mesh.Vertex]()
var indices = [UInt16]()
let readIndex =
{ (v: ObjModel.Index) -> UInt16 in
let vertex = Mesh.Vertex(
position: model.positions[v.p],
normal: model.normals[v.n],
texCoord: model.texCoords[v.t])
if let index = vertices.firstIndex(of: vertex)
{
indices.append(UInt16(index))
return UInt16(index)
}
else
{
let index = UInt16(vertices.count)
indices.append(index)
vertices.append(vertex)
return index
}
}
for mesh in model.objects
{
let start = indices.count
for face: ObjModel.Face in mesh.value.faces
{
switch face
{
case .triangle(let v1, let v2, let v3):
for v in [ v1, v2, v3 ] { _ = readIndex(v) }
case .quad(let v1, let v2, let v3, let v4):
let n1 = readIndex(v1)
_ = readIndex(v2)
indices.append(readIndex(v3))
_ = readIndex(v4)
indices.append(n1)
case .ngon(_): fallthrough
default: break
}
}
let length = indices.count - start
if length > 0
{
subMeshes[mesh.key] = .init(start: start, length: length)
}
}
return Mesh(vertices: vertices, indices: indices, subMeshes: subMeshes)
}
}

View File

@ -0,0 +1,372 @@
import Foundation
public struct ObjReader
{
public static func read(url: URL) throws -> ObjModel
{
var file = try ObjDocumentReader(filePath: url)
var model = ObjModel()
var materials = Dictionary<String, ObjMaterial>()
var name: String? = nil
var object = ObjModel.Object()
file.string(tag: "mtllib") { s in
let mats = try ObjMtlLoader.read(url: url.deletingLastPathComponent().appendingPathComponent(s[0]))
materials.merge(mats, uniquingKeysWith: { (_, new) in new } )
}
file.string(tag: "o", count: 1) { s in
if !object.faces.isEmpty
{
model.objects[name!] = object
object = .init()
}
name = String(s[0])
}
file.float(tag: "v", count: 3) { v in model.positions.append(Vec3f(v[0], v[1], v[2])) }
file.float(tag: "vn", count: 3) { v in model.normals.append(Vec3f(v[0], v[1], v[2])) }
file.float(tag: "vt", count: 2) { v in model.texCoords.append(Vec2f(v[0], v[1])) }
file.raw(tag: "f") { raw in
let readIndex =
{ (raw: Substring) in
let face = raw.split(separator: "/")
guard face.count == 3,
let posIdx = Int(face[0]), let coordIdx = Int(face[1]), let normIdx = Int(face[2])
else { throw ObjLoaderError.badTagParameters }
return ObjModel.Index(p: posIdx - 1, n: normIdx - 1, t: coordIdx - 1)
}
if raw.count == 3
{
for raw in raw { _ = try readIndex(raw) }
object.faces.append(.triangle(
v1: try readIndex(raw[raw.startIndex]),
v2: try readIndex(raw[raw.startIndex + 1]),
v3: try readIndex(raw[raw.startIndex + 2])))
}
else if raw.count == 4
{
object.faces.append(.quad(
v1: try readIndex(raw[raw.startIndex]),
v2: try readIndex(raw[raw.startIndex + 1]),
v3: try readIndex(raw[raw.startIndex + 2]),
v4: try readIndex(raw[raw.startIndex + 3])))
}
else if raw.count >= 5
{
object.faces.append(.ngon(try raw.map { try readIndex($0) }))
}
else { throw ObjLoaderError.badTagParameters }
}
file.int(tag: "l") { i in object.faces.append(.line(p1: i[0], p2: i[1])) }
try file.read()
if !object.faces.isEmpty
{
model.objects[name!] = object
}
return model
}
}
fileprivate struct ObjMtlLoader
{
static func read(url: URL) throws -> Dictionary<String, ObjMaterial>
{
var file = try ObjDocumentReader(filePath: url)
var materials = Dictionary<String, ObjMaterial>()
var name: String = ""
var mat: ObjMaterial? = nil
file.string(tag: "newmtl", count: 1)
{ s in
if mat != nil
{
materials[name] = mat!
}
mat = .init()
name = String(s[0])
}
file.preHandle { s in if s != "newmtl" && mat == nil { throw ObjLoaderError.unexpectedTag } }
file.float(tag: "Ka", count: 3) { f in mat!.ambient = Colour(r: f[0], g: f[1], b: f[2]) } // "Ambient colour"
file.float(tag: "Kd", count: 3) { f in mat!.diffuse = Colour(r: f[0], g: f[1], b: f[2]) } // "Diffuse colour"
file.float(tag: "Ks", count: 3) { f in mat!.specular = Colour(r: f[0], g: f[1], b: f[2]) } // "Specular colour"
file.float(tag: "Ns", count: 1) { f in mat!.specularExp = f[0] } // "Specular exponent/shininess"
file.float(tag: "Ni", count: 1) { f in mat!.refraction = f[0] } // "Optical density/refraction index"
file.float(tag: "d", count: 1) { f in mat!.alpha = f[0] } // "Dissolve" 0.0 = transparent, 1.0 = opaque
file.float(tag: "Tr", count: 1) { f in mat!.alpha = 1.0 - f[0] } // "Transparent" 0.0 = opaque, 1.0 = transparent
file.int(tag: "illum", count: 1) { i in mat!.model = try .resolve(i[0]) } // "Illumination model"
file.textureMap(tag: "map_Ka") { s in } // "Ambient colourmap"
file.textureMap(tag: "map_Kd") { t in mat!.diffuseMap = t } // "Albedo map"
file.textureMap(tag: "map_Ks") { t in mat!.specularMap = t } // "Specular colourmap"
file.textureMap(tag: "map_Ns") { t in mat!.specularExpMap = t } // "Specular exponent map"
file.textureMap(tag: "map_d") { t in mat!.dissolveMap = t } // "Dissolve map"
//file.string(tag: "map_Tr") { t in } // "Translucency map"
for t in ["map_Bump", "bump"] { file.textureMap(tag: t) { t in mat!.bumpMap = t } } // "Bump map"
file.textureMap(tag: "disp") { t in mat!.displacementMap = t } // "Displacement map"
file.textureMap(tag: "decal") { t in mat!.decalMap = t } // ?
// PBR extensions
file.float(tag: "Pr", count: 1) { f in mat!.roughness = f[0] } // "Roughness"
file.float(tag: "Pm", count: 1) { f in mat!.metallic = f[0] } // "Metallic"
file.float(tag: "Ps", count: 1) { f in mat!.sheen = f[0] } // "Sheen"
file.float(tag: "Pc", count: 1) { f in } // "Clearcoat thickness"
file.float(tag: "Pcr", count: 1) { f in } // "Clearcoat roughness"
file.float(tag: "Ke", count: 3) { f in } // "Emmission colour"
file.float(tag: "aniso", count: 1) { f in } // "Anisotropy"
file.float(tag: "anisor", count: 1) { f in } // "Anisotropy rotation"
file.textureMap(tag: "map_Pr") { t in mat!.roughnessMap = t } // "Roughness texturemap"
file.textureMap(tag: "map_Pm") { t in mat!.metallicMap = t } // "Metallic texturemap"
file.textureMap(tag: "map_Ps") { t in mat!.sheenMap = t } // "Sheen texturemap"
file.textureMap(tag: "map_Ke") { t in mat!.emmisionMap = t } // "Emmision texturemap"
file.textureMap(tag: "norm") { t in mat!.normalMap = t } // "Normal map"
try file.read()
if let material = mat
{
materials[name] = material
}
return materials
}
}
fileprivate extension ObjMaterial.IlluminationModel
{
static func resolve(_ id: Int) throws -> Self
{
switch id
{
case 0: .colour
case 1: .lambert
case 2: .blinnPhong
case 3: .reflectionRaytrace
case 4: .transparentGlassReflectionRaytrace
case 5: .reflectionRaytraceFresnel
case 6: .transparentRefractionReflectionRaytrace
case 7: .transparentRefractionReflectionRaytraceFresnel
case 8: .reflection
case 9: .transparentGlassReflection
case 10: .shadowOnly
default: throw ObjLoaderError.badTagParameters
}
}
}
fileprivate extension ObjMaterial.TextureMap
{
static func parse(_ argstr: Substring) throws -> Self
{
var map = Self()
var args = [Substring]()
let parseFlag =
{ (flag: Flags) in
let arg = args[0].lowercased()
if arg == "on" { map.flags.insert(flag) }
else if arg == "off" { map.flags.remove(flag) }
else { throw ObjLoaderError.badTagParameters }
}
let parseInt =
{ (arg: Int) in
guard let result = Int(args[arg]) else { throw ObjLoaderError.badTagParameters }
return result
}
let parseFloat =
{ (arg: Int) in
guard let result = Float(args[arg]) else { throw ObjLoaderError.badTagParameters }
return result
}
let parseVector = { Vec3f(try parseFloat(0), try parseFloat(1), try parseFloat(2)) }
typealias Option = () throws -> Void
let options: Dictionary<String, (Int, Option)> =
[
"blendu": (1, { try parseFlag(.blendHoriz) }),
"blendv": (1, { try parseFlag(.blendVert) }),
"bm": (1, { map.blendMul = try parseFloat(0) }),
"cc": (1, { try parseFlag(.colourCorrection) }),
"clamp": (1, { try parseFlag(.clamp) }),
"imfchan": (1, { map.imfChan = try .resolve(args[0]) }),
"mm": (2, { map.mmBaseGain = (try parseFloat(0), try parseFloat(1)) }),
"o": (3, { map.offset = try parseVector() }),
"s": (3, { map.scale = try parseVector() }),
"t": (3, { map.turbulence = try parseVector() }),
"texres": (1, { map.textureResolution = try parseInt(0) })
]
var expectArgs = 0, option: Option? = nil
var index = argstr.startIndex
repeat
{
if expectArgs > 0, let callback = option
{
let start = index
index = argstr[start...].firstIndex(where: \.isWhitespace) ?? argstr.endIndex
args.append(argstr[start..<index])
expectArgs -= 1
if expectArgs == 0
{
try callback()
option = nil
}
}
else if argstr[index] == "-"
{
let start = argstr.index(after: index)
index = argstr[start...].firstIndex(where: \.isWhitespace) ?? argstr.endIndex
let name = String(argstr[start..<index])
guard let params = options[name] else { throw ObjLoaderError.badTagParameters }
(expectArgs, option) = params
}
else
{
break
}
index = argstr[index...].firstIndex { !$0.isWhitespace } ?? argstr.endIndex
}
while index < argstr.endIndex
if index >= argstr.endIndex || expectArgs > 0 { throw ObjLoaderError.badTagParameters }
map.path = String(argstr[index...])
return map
}
}
fileprivate extension ObjMaterial.TextureMap.ImfChan
{
static func resolve(_ token: Substring) throws -> Self
{
switch token
{
case "r": .r
case "g": .g
case "b": .b
case "m": .m
case "l": .l
case "z": .z
default: throw ObjLoaderError.badTagParameters
}
}
}
fileprivate struct ObjDocumentReader
{
private enum Handler
{
case string(closure: ([String]) throws -> Void, count: Int? = nil)
case float(closure: ([Float]) throws -> Void, count: Int? = nil)
case int(closure: ([Int]) throws -> Void, count: Int? = nil)
case textureMap(closure: (ObjMaterial.TextureMap) throws -> Void)
case raw(closure: ([Substring]) throws -> Void)
}
private var file: TextFile
typealias Callback = (Substring) throws -> Void
private var _prehandler: Callback? = nil
private var handlers = Dictionary<String, Handler>()
init(filePath: URL) throws
{
file = try TextFile(fileURL: filePath)
}
mutating func string(tag: String, count: Int? = nil, closure: @escaping ([String]) throws -> Void)
{
handlers[tag] = .string(closure: closure, count: count)
}
mutating func float(tag: String, count: Int? = nil, closure: @escaping ([Float]) throws -> Void)
{
handlers[tag] = .float(closure: closure, count: count)
}
mutating func int(tag: String, count: Int? = nil, closure: @escaping ([Int]) throws -> Void)
{
handlers[tag] = .int(closure: closure, count: count)
}
mutating func textureMap(tag: String, closure: @escaping (ObjMaterial.TextureMap) throws -> Void)
{
handlers[tag] = .textureMap(closure: closure)
}
mutating func raw(tag: String, closure: @escaping ([Substring]) throws -> Void)
{
handlers[tag] = .raw(closure: closure)
}
mutating func preHandle(closure: @escaping Callback) { _prehandler = closure }
private func handle(_ handler: Handler, command: Substring, arg: Substring) throws
{
switch handler
{
case .string(let closure, let count):
let args = arg.split(separator: " ")
if count != nil && args.count != count! { throw ObjLoaderError.badTagParameters }
try closure(args.map({ String($0) }))
case .float(let closure, let count):
let args = arg.split(separator: " ")
if count != nil && args.count != count! { throw ObjLoaderError.badTagParameters }
try closure(args.map(
{
if let value = Float($0) { return value }
else { throw ObjLoaderError.badTagParameters }
}))
case .int(let closure, let count):
let args = arg.split(separator: " ")
if count != nil && args.count != count! { throw ObjLoaderError.badTagParameters }
try closure(args.map(
{
if let value = Int($0) { return value }
else { throw ObjLoaderError.badTagParameters }
}))
case .textureMap(let closure):
try closure(ObjMaterial.TextureMap.parse(arg))
case .raw(let closure):
try closure(arg.split(separator: " "))
}
}
private func parseLine(_ line: String) throws -> (Substring, Substring)?
{
// Trim comment if present
var trimmed = if let index = line.firstIndex(of: "#") { line[..<index] } else { line[...] }
// Trim leading and trailing whitespace
guard let start = trimmed.firstIndex(where: { !$0.isWhitespace }),
let end = trimmed.lastIndex(where: { !$0.isWhitespace })
else { return nil }
trimmed = trimmed[start...end]
// Split command & rest of string as arguments
guard let cmdEnd = trimmed.firstIndex(where: \.isWhitespace),
let argStart = trimmed.suffix(from: trimmed.index(after: cmdEnd)).firstIndex(where: { !$0.isWhitespace })
else { throw ObjLoaderError.badTagParameters }
return (trimmed.prefix(upTo: cmdEnd), trimmed.suffix(from: argStart))
}
func read() throws
{
for line in file.lines
{
if !line.isEmpty,
let (cmd, argstr) = try parseLine(line),
let handler = handlers[String(cmd)]
{
if let prehandler = _prehandler { try prehandler(cmd) }
try handle(handler, command: cmd, arg: argstr)
}
}
}
}
enum ObjLoaderError: Error
{
case badTagParameters
case unexpectedTag
}

View File

@ -0,0 +1,291 @@
import Foundation
import simd
public extension FloatingPoint
{
@inline(__always) var saturate: Self { min(max(self , 0), 1) }
@inline(__always) static func lerp(_ a: Self, _ b: Self, _ x: Self) -> Self { a * (1 - x) + b * x }
@inline(__always) static func deg(fromRad: Self) -> Self { fromRad * (180 / Self.pi) }
@inline(__always) static func rad(fromDeg: Self) -> Self { fromDeg * (Self.pi / 180) }
fileprivate func axisDeadzone(_ min: Self, _ max: Self) -> Self
{
let xabs = abs(self)
return if xabs <= min { 0 }
else if xabs >= max { Self(signOf: self, magnitudeOf: 1) }
else { Self(signOf: self, magnitudeOf: xabs - min) / (max - min) }
}
}
public extension SIMD2 where Scalar: FloatingPoint
{
@inline(__always) var len2: Scalar { x * x + y * y }
//@inline(__always) var len2: Scalar { simd_dot(self, self) }
@inline(__always) var len: Scalar { len2.squareRoot() }
@inline(__always) var normalised: Self { self / len }
@inline(__always) func dot(_ b: Self) -> Scalar { x * b.x + y * b.y }
@inline(__always) func reflect(_ n: Self) -> Self { self - (n * 2 * self.dot(n)) }
@inline(__always) func project(_ n: Self) -> Self { n * self.dot(n) }
@inline(__always) func cross(_ b: Self) -> Scalar { x * b.y - y * b.x }
@inline(__always) func lerp(_ b: Self, _ x: Scalar) -> Self
{
let invX = 1 - x
return Self(self.x * invX + b.x * x, self.y * invX + b.y * x)
}
func cardinalDeadzone(min: Scalar, max: Scalar) -> Self
{
Self(self.x.axisDeadzone(min, max), self.y.axisDeadzone(min, max))
}
func radialDeadzone(min: Scalar, max: Scalar) -> Self
{
let magnitude = self.len
if magnitude == .zero || magnitude < min { return Self.zero }
if magnitude > max { return self / magnitude }
let rescale = (magnitude - min) / (max - min)
return self / magnitude * rescale
}
}
public extension SIMD3 where Scalar: FloatingPoint
{
@inline(__always) static var X: Self { Self(1, 0, 0) }
@inline(__always) static var Y: Self { Self(0, 1, 0) }
@inline(__always) static var Z: Self { Self(0, 0, 1) }
@inline(__always) static var up: Self { Y }
@inline(__always) static var down: Self { -Y }
@inline(__always) static var left: Self { -X }
@inline(__always) static var right: Self { X }
@inline(__always) static var forward: Self { Z }
@inline(__always) static var back: Self { -Z }
@inline(__always) var len2: Scalar { x * x + y * y + z * z }
@inline(__always) var len: Scalar { len2.squareRoot() }
@inline(__always) var normalised: Self { self / len }
@inline(__always) mutating func normalise() { self /= len }
@inline(__always) func lerp(_ b: Self, _ x: Scalar) -> Self
{
let invX = 1 - x
return Self(self.x * invX + b.x * x, self.y * invX + b.y * x, self.z * invX + b.z * x)
}
@inline(__always) func dot(_ b: Self) -> Scalar { x * b.x + y * b.y + z * b.z }
@inline(__always) func cross(_ b: Self) -> Self { Self(y * b.z - z * b.y, z * b.x - x * b.z, x * b.y - y * b.x) }
@inline(__always) func project(_ n: Self) -> Self { n * self.dot(n) }
}
public extension SIMD4 where Scalar: FloatingPoint
{
@inline(__always) static var X: Self { Self(1, 0, 0, 0) }
@inline(__always) static var Y: Self { Self(0, 1, 0, 0) }
@inline(__always) static var Z: Self { Self(0, 0, 1, 0) }
@inline(__always) static var W: Self { Self(0, 0, 0, 1) }
}
public extension simd_float4x4
{
@inline(__always) static var identity: Self { Self(diagonal: .one) }
@inline(__always) static func translate(_ v: Vec3f) -> Self
{
Self(
.init( 1, 0, 0, 0),
.init( 0, 1, 0 ,0),
.init( 0, 0, 1, 0),
.init(v.x, v.y, v.z, 1))
}
@inline(__always) static func scale(_ v: Vec3f) -> Self
{
Self(
.init(v.x, 0, 0, 0),
.init( 0, v.y, 0, 0),
.init( 0, 0, v.z, 0),
.init( 0, 0, 0, 1))
}
@inline(__always) static func scale(scalar v: Float) -> Self
{
Self(
.init(v, 0, 0, 0),
.init(0, v, 0, 0),
.init(0, 0, v, 0),
.init(0, 0, 0, 1))
}
static func rotate(axis v: Vec3f, angle theta: Float) -> Self
{
//FIXME: THIS IS FUCKED UP FOR EVERYTHING OTHER THAN X AXIS ROTATION LOL
/*
let vv = v * v
let xy = v.x * v.y, xz = v.x * v.z, yz = v.y * v.z
let ts = sin(angle), tc = cos(angle)
return Self(
.init(
vv.x + (tc * (1 - vv.x)),
(xz - (tc * xy)) + (ts * v.z),
(xz - (tc * xz)) - (ts * v.y), 0),
.init(
(xy - (tc * xy)) - (ts * v.z),
vv.y + (tc * (1 - vv.y)),
(xz - (tc * xz)) + (ts * v.x), 0),
.init(
(xz - (tc * xz)) + (ts * v.y),
(xz - (tc * xz)) - (ts * v.y),
vv.z + (tc * (1 - vv.z)), 1),
.init(0, 0, 0, 1))
*/
let vxx = v.x * v.x, vxy = v.x * v.y, vxz = v.x * v.z
let vyy = v.y * v.y, vyz = v.y * v.z
let vzz = v.z * v.z
let ts = sin(theta), tc = cos(theta)
let ic = 1 - tc
return Self(
.init(
ic * vxx + tc,
ic * vxy - v.z * ts,
ic * vxz + v.z * ts,
0),
.init(
ic * vxy + v.z * ts,
ic * vyy + tc,
ic * vyz - v.x * ts,
0),
.init(
ic * vxz - v.y * ts,
ic * vyz + v.x * ts,
ic * vzz + tc,
0),
.init(0, 0, 0, 1))
}
@inline(__always) static func rotate(yawPitch: Vec2f) -> Self { return rotate(yaw: yawPitch.x, pitch: yawPitch.y) }
static func rotate(yaw ytheta: Float, pitch xtheta: Float) -> Self
{
let xc = cos(xtheta), xs = sin(xtheta)
let yc = cos(ytheta), ys = sin(ytheta)
return Self(
.init(yc, ys * xs, -ys * xc, 0),
.init( 0, xc, xs, 0),
.init(ys, yc * -xs, yc * xc, 0),
.init( 0, 0, 0, 1))
}
static func rotate(yaw ytheta: Float, pitch xtheta: Float, roll ztheta: Float) -> Self
{
//FIXME: this doesn't null against control
let xc = cos(xtheta), xs = sin(xtheta)
let yc = cos(ytheta), ys = sin(ytheta)
let zc = cos(ztheta), zs = sin(ztheta)
let ysxs = ys * xs, ycxs = yc * xs
let result = Mat4f(
.init(yc * zc + ysxs * zs, yc * -zs + ysxs * zc, -ys * xc, 0),
.init( xc * zs, xc * zc, xs, 0),
.init(ys * zc - ycxs * zs, ys * -zs - ycxs * zc, yc * xc, 0),
.init( 0, 0, 0, 1))
let shouldBe = .rotate(z: ztheta) * .rotate(x: xtheta) * .rotate(y: ytheta)
let epsilon: Float = .ulpOfOne
if (result != shouldBe)
{
assert(abs(result[0][0] - shouldBe[0][0]) <= epsilon) // epsilon
assert(result[1][0] == shouldBe[1][0])
assert(abs(result[2][0] - shouldBe[2][0]) <= epsilon) // epsilon
assert(result[3][0] == shouldBe[3][0])
assert(abs(result[0][1] - shouldBe[0][1]) <= epsilon) // epsilon
assert(result[1][1] == shouldBe[1][1])
assert(abs(result[2][1] - shouldBe[2][1]) <= epsilon) // epsilon
assert(result[3][1] == shouldBe[3][1])
assert(result[0][2] == shouldBe[0][2])
assert(result[1][2] == shouldBe[1][2])
assert(result[2][2] == shouldBe[2][2])
assert(result[3][2] == shouldBe[3][2])
assert(result[0][3] == shouldBe[0][3])
assert(result[1][3] == shouldBe[1][3])
assert(result[2][3] == shouldBe[2][3])
assert(result[3][3] == shouldBe[3][3])
}
return result
}
@inline(__always) static func rotate(x theta: Float) -> Self
{
let c = cos(theta), s = sin(theta)
return Self(
.init(1, 0, 0, 0),
.init(0, c, s, 0),
.init(0, -s, c, 0),
.init(0, 0, 0, 1))
}
@inline(__always) static func rotate(y theta: Float) -> Self
{
let c = cos(theta), s = sin(theta)
return Self(
.init(c, 0, -s, 0),
.init(0, 1, 0, 0),
.init(s, 0, c, 0),
.init(0, 0, 0, 1))
}
@inline(__always) static func rotate(z theta: Float) -> Self
{
let c = cos(theta), s = sin(theta)
return Self(
.init(c, -s, 0, 0),
.init(s, c, 0, 0),
.init(0, 0, 1, 0),
.init(0, 0, 0, 1))
}
static func perspective(fovY: Float, aspect: Float, zNear: Float, zFar: Float) -> Self
{
let h = 1 / tanf(fovY * 0.5)
let w = h / aspect
let invClipRange = 1 / (zFar - zNear)
let z = -(zFar + zNear) * invClipRange
let z2 = -(2 * zFar * zNear) * invClipRange
return simd_matrix(
.init(w, 0, 0, 0),
.init(0, h, 0, 0),
.init(0, 0, z, -1),
.init(0, 0, z2, 0))
}
static func lookAt(from: Vec3f = .zero, to: Vec3f, up: Vec3f = .up) -> Self
{
let forward = (to - from).normalised
let bitangent = forward.cross(up).normalised
let tangent = bitangent.cross(forward).normalised
let normal = -forward
return simd_matrix(
.init(bitangent.x, tangent.x, normal.x, 0.0),
.init(bitangent.y, tangent.y, normal.y, 0.0),
.init(bitangent.z, tangent.z, normal.z, 0.0),
.init( 0.0, 0.0, 0.0, 1.0))
}
}
public typealias Vec2f = SIMD2<Float>
public typealias Vec2d = SIMD2<Double>
public typealias Vec3f = SIMD3<Float>
public typealias Vec3d = SIMD3<Double>
public typealias Vec4f = SIMD4<Float>
public typealias Vec4d = SIMD4<Double>
public typealias Mat4f = simd_float4x4

View File

@ -0,0 +1,81 @@
public enum Fog
{
public enum Mode
{
case distance
case depth
}
public enum RangeType
{
case linear
case smooth
}
public enum FactorType
{
case exp
case exp2
}
case none
case range(colour: Colour, mode: Mode, type: RangeType, start: Float, end: Float)
case factor(colour: Colour, mode: Mode, type: FactorType, density: Float)
}
public enum Light
{
case directional(colour: Colour, direction: Vec3f)
case point(colour: Colour, position: Vec3f, intensity: Float)
}
public struct Environment
{
public var fog: Fog = .none
public var ambient: Colour = .black
public var lights: [Light] = .init()
public init() {}
public init(fog: Fog, ambient: Colour, lights: [Light])
{
self.fog = fog
self.ambient = ambient
self.lights = lights
}
}
public extension Environment
{
enum FogFalloff
{
case range(type: Fog.RangeType, start: Float, end: Float)
case factor(type: Fog.FactorType, density: Float)
}
mutating func setFog(colour: Colour, mode: Fog.Mode, falloff: FogFalloff)
{
fog = switch falloff
{
case .range(let type, let start, let end):
.range(colour: colour, mode: mode, type: type, start: start, end: end)
case .factor(let type, let density):
.factor(colour: colour, mode: mode, type: type, density: density)
}
}
mutating func addAmbientLight(colour: Colour)
{
ambient = colour
}
mutating func addDirectionalLight(direction: Vec3f, colour: Colour)
{
lights.append(.directional(colour: colour, direction: direction))
}
mutating func addPointLight(position: Vec3f, colour: Colour, intensity: Float)
{
lights.append(.point(colour: colour, position: position, intensity: intensity))
}
}

View File

@ -0,0 +1,20 @@
public struct Material
{
public var id: String
public var diffuse: Colour, specular: Colour
public var specularExp: Float
public var texture: RenderTexture2D
public init(id: String = "",
diffuse: Colour = .white,
specular: Colour = .zero,
gloss: Float = 0,
texture: RenderTexture2D = .empty)
{
self.id = id
self.diffuse = diffuse
self.specular = specular
self.specularExp = gloss
self.texture = texture
}
}

View File

@ -0,0 +1,440 @@
//#set OPENGL3
import Foundation
import SDL2
#if OPENGL3
import OpenGL
#else
import OpenGL.GL3
#endif
class OpenGL: Renderer
{
private let srgb = true
private var glCtx: SDL_GLContext? = nil
private var state = OpenGLState()
struct Version { let major, minor: Int32 }
let glVersion: Version
init(version: Version)
{
self.glVersion = version
}
func create(sdlWindow: OpaquePointer) throws
{
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, Int32(SDL_GL_CONTEXT_PROFILE_COMPATIBILITY.rawValue))
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, glVersion.major)
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, glVersion.minor)
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1)
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0)
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 0)
if let context = SDL_GL_CreateContext(sdlWindow) { glCtx = context }
else { throw RendererError.sdlError(message: "SDL_GL_CreateContext: \(String(cString: SDL_GetError()))") }
guard SDL_GL_MakeCurrent(sdlWindow, glCtx) == 0
else { throw RendererError.sdlError(message: "SDL_GL_MakeCurrent: \(String(cString: SDL_GetError()))") }
state.enable([.texture2D, .cullFace, .depthTest, .rescaleNormal, .colourMaterial])
if srgb { state.enable(.frameBufferSrgb) }
state.cullFace = .back
state.clearDepth = 1
state.depthFunc = .less
state.setHint(target: .fog, hint: .dontCare)
}
func delete()
{
SDL_GL_DeleteContext(glCtx)
}
func resize(width: Int32, height: Int32)
{
glViewport(0, 0, width, height)
}
func newFrame()
{
glClear(GLbitfield(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT))
}
private var _clearColour = Colour.black
var clearColour: Colour
{
get { _clearColour }
set(newColour)
{
state.clearColour(srgb ? newColour.linear : newColour)
_clearColour = newColour
}
}
func setVsync(mode: VSyncMode) throws
{
guard SDL_GL_SetSwapInterval(mode.sdlInterval) == 0
else { throw RendererError.sdlError(message: "SDL_GL_SetSwapInterval: \(String(cString: SDL_GetError()))") }
}
func createMesh(mesh: Mesh) throws -> RenderMesh
{
var buffers = [GLuint](repeating: 0, count: 2)
buffers.withUnsafeMutableBufferPointer
{
glGenBuffers(2, $0.baseAddress!)
}
state.arrayBuffer = buffers[0]
state.elementArrayBuffer = buffers[1]
glBufferData(GLenum(GL_ARRAY_BUFFER),
MemoryLayout<Mesh.Vertex>.stride * mesh.vertices.count,
mesh.vertices, GLenum(GL_STATIC_DRAW))
glBufferData(GLenum(GL_ELEMENT_ARRAY_BUFFER),
MemoryLayout<Mesh.Index>.stride * mesh.indices.count,
mesh.indices, GLenum(GL_STATIC_DRAW))
state.elementArrayBuffer = OpenGLState.defaultBuffer
state.arrayBuffer = OpenGLState.defaultBuffer
var subMeshes = [Mesh.SubMesh]()
if mesh.subMeshes.isEmpty
{
subMeshes.append(Mesh.SubMesh(start: 0, length: mesh.indices.count))
}
else
{
for subMesh in mesh.subMeshes
{
if ["Collision", "Collision3D"].contains(subMesh.key) { continue }
subMeshes.append(Mesh.SubMesh(
start: subMesh.value.start,
length: subMesh.value.length))
}
}
return RenderMesh(
vbo: Int(buffers[0]),
ibo: Int(buffers[1]),
subMeshes: subMeshes)
}
func createTexture(data: UnsafeRawPointer, width: Int, height: Int) throws -> RenderTexture2D
{
try createTexture(data: data, width: width, height: height, filter: .linear, mipMode: .off)
}
func createTexture(data: UnsafeRawPointer, width: Int, height: Int,
filter: FilterMode, mipMode: MipMode) throws -> RenderTexture2D
{
let min: Int32 = switch mipMode
{
case .off: filter.gl
case .nearest: filter.glMip
case .linear: filter.glLinearMip
}
return RenderTexture2D(try loadTexture(data: data,
width: GLsizei(width), height: GLsizei(height),
minFilter: min, magFilter: filter.gl))
}
private func loadTexture(
data: UnsafeRawPointer,
width: GLsizei, height: GLsizei,
minFilter: Int32 = GL_LINEAR, magFilter: Int32 = GL_LINEAR,
wrap: Int32 = GL_REPEAT) throws -> GLuint
{
// Create & upload raw bytes as texture
var texId: GLuint = 0
glGenTextures(1, &texId)
state.bindTexture2D(active: 0, texture: texId)
state.texture2DParameter(active: 0, param: .minFilter, int: minFilter)
state.texture2DParameter(active: 0, param: .magFilter, int: magFilter)
state.texture2DParameter(active: 0, param: .wrapS, int: wrap)
state.texture2DParameter(active: 0, param: .wrapT, int: wrap)
let format: GLint = srgb ? GL_SRGB8 : GL_RGBA
glTexImage2D(GLenum(GL_TEXTURE_2D),
0, format, width, height, 0, GLenum(GL_RGBA), GLenum(GL_UNSIGNED_BYTE), data)
// Generate mipmaps if needed
if [GL_NEAREST_MIPMAP_LINEAR, GL_NEAREST_MIPMAP_NEAREST, GL_LINEAR_MIPMAP_LINEAR, GL_LINEAR_MIPMAP_NEAREST]
.contains(minFilter) { glGenerateMipmap(GLenum(GL_TEXTURE_2D)) }
state.bindTexture2D(active: 0, texture: OpenGLState.defaultTexture)
return texId
}
func deleteMesh(_ mesh: RenderMesh)
{
var buffers = [GLuint](repeating: 0, count: 2)
buffers[0] = GLuint(mesh.iboHnd)
buffers[1] = GLuint(mesh.vboHnd)
glDeleteBuffers(1, buffers)
}
func deleteTexture(_ texture: RenderTexture2D)
{
var texId = GLuint(texture.id)
glDeleteTextures(1, &texId)
}
func setProjection(matrix: Mat4f)
{
state.matrixMode = .projection
loadMatrix(matrix)
}
func setView(matrix: Mat4f)
{
state.matrixMode = .modelView
loadMatrix(matrix)
}
func setMaterial(_ mat: Material)
{
glColor4f(mat.diffuse.r, mat.diffuse.g, mat.diffuse.b, mat.diffuse.a)
state.bindTexture2D(active: 0, texture: mat.texture.isValid ? mat.texture.id : 0)
}
private func draw(subMesh: Mesh.SubMesh)
{
glDrawElements(
GLenum(GL_TRIANGLES),
GLsizei(subMesh.length),
GLenum(GL_UNSIGNED_SHORT),
.init(bitPattern: MemoryLayout<Mesh.Index>.stride * subMesh.start));
}
private func draw(mesh: RenderMesh)
{
state.enableClient([ .vertex, .normal, .textureCoord ])
state.arrayBuffer = UInt32(mesh.vboHnd)
state.elementArrayBuffer = UInt32(mesh.iboHnd)
let stride = GLsizei(MemoryLayout<Mesh.Vertex>.stride)
glVertexPointer(3, GLenum(GL_FLOAT), stride,
UnsafeRawPointer.init(bitPattern: MemoryLayout.offset(of: \Mesh.Vertex.position)!))
glNormalPointer(GLenum(GL_FLOAT), stride,
UnsafeRawPointer.init(bitPattern: MemoryLayout.offset(of: \Mesh.Vertex.normal)!))
glTexCoordPointer(2, GLenum(GL_FLOAT), stride,
UnsafeRawPointer.init(bitPattern: MemoryLayout.offset(of: \Mesh.Vertex.texCoord)!))
for subMesh in mesh.subMeshes
{
draw(subMesh: subMesh)
}
state.elementArrayBuffer = OpenGLState.defaultBuffer
state.arrayBuffer = OpenGLState.defaultBuffer
state.disableClient([ .vertex, .normal, .textureCoord ])
}
func draw(mesh: RenderMesh, model: Mat4f, environment env: Environment)
{
if (mesh.subMeshes.isEmpty) { return }
applyEnvironment(env)
state.matrixMode = .modelView
glPushMatrix()
mulMatrix(model)
draw(mesh: mesh)
glPopMatrix()
}
func draw(mesh: RenderMesh, environment env: Environment)
{
if (mesh.subMeshes.isEmpty) { return }
applyEnvironment(env)
draw(mesh: mesh)
}
private func loadMatrix(_ matrix: Mat4f)
{
withUnsafePointer(to: matrix.columns)
{
$0.withMemoryRebound(to: GLfloat.self, capacity: 16)
{ (values: UnsafePointer<GLfloat>) in
glLoadMatrixf(values)
}
}
}
private func mulMatrix(_ matrix: Mat4f)
{
withUnsafePointer(to: matrix.columns)
{
$0.withMemoryRebound(to: GLfloat.self, capacity: 16)
{ (values: UnsafePointer<GLfloat>) in
glMultMatrixf(values)
}
}
}
private func applyEnvironment(_ env: Environment)
{
let coordSource =
{ (mode: Fog.Mode) -> OpenGLState.FogCoordinateSource in
switch mode
{
case .depth: .fragmentDepth
case .distance: .coordinate
}
}
switch env.fog
{
case .none:
state.disable(.fog)
case .range(let colour, let mode, _, let start, let end):
state.fogMode = .linear
state.fogColour = srgb ? colour.linear : colour
state.fogCoodinateSource = coordSource(mode)
state.fogStart = start
state.fogEnd = end
state.enable(.fog)
break
case .factor(let colour, let mode, let type, let density):
state.fogMode = switch type
{
case .exp: .exp
case .exp2: .exp2
}
state.fogColour = srgb ? colour.linear : colour
state.fogCoodinateSource = coordSource(mode)
state.fogDensity = density
state.enable(.fog)
break
}
state.lightModelAmbient = env.ambient
let lightCaps: [OpenGLState.Capability] = [
.light0, .light1, .light2, .light3,
.light4, .light5, .light6, .light7 ]
if !env.lights.isEmpty
{
state.enable(.lighting)
for i in 0..<8
{
if i < env.lights.count
{
switch env.lights[i]
{
case .directional(let colour, let direction):
state.lightPosition[i] = Vec4f(direction, 0)
state.lightDiffuse[i] = srgb ? colour.linear : colour
case .point(let colour, let position, let intensity):
state.lightPosition[i] = Vec4f(position, 1)
state.lightDiffuse[i] = srgb ? colour.linear : colour
state.lightConstantAttenuation[i] = 0
state.lightLinearAttenuation[i] = intensity
state.lightQuadraticAttenuation[i] = 0
}
state.enable(lightCaps[i])
}
else
{
state.disable(lightCaps[i])
}
}
}
else
{
state.disable(.lighting)
for cap in lightCaps { state.disable(cap) }
}
}
func drawGizmos(lines: [Line])
{
state.disable([ .texture2D, .depthTest ])
var gizmoEnv = Environment()
gizmoEnv.fog = .none
applyEnvironment(gizmoEnv)
glBegin(GLenum(GL_LINES))
for line in lines
{
let colour = srgb ? line.colour.linear : line.colour
glColor4f(colour.r, colour.g, colour.b, colour.a)
glVertex3f(line.from.x, line.from.y, line.from.z)
glVertex3f(line.to.x, line.to.y, line.to.z)
}
glEnd()
state.enable([ .texture2D, .depthTest ])
}
}
extension VSyncMode
{
fileprivate var sdlInterval: Int32
{
switch self
{
case .off: 0
case .on: 1
case .adaptive: -1
}
}
}
extension FilterMode
{
fileprivate var gl: Int32
{
switch self
{
case .point: GL_NEAREST
case .linear: GL_LINEAR
}
}
fileprivate var glMip: Int32
{
switch self
{
case .point: GL_NEAREST_MIPMAP_NEAREST
case .linear: GL_LINEAR_MIPMAP_NEAREST
}
}
fileprivate var glLinearMip: Int32
{
switch self
{
case .point: GL_NEAREST_MIPMAP_LINEAR
case .linear: GL_LINEAR_MIPMAP_LINEAR
}
}
}
extension WrapMode
{
fileprivate var gl: Int32
{
switch self
{
case .clamping: GL_CLAMP
case .clampBorder: GL_CLAMP_TO_BORDER
case .clampEdge: GL_CLAMP_TO_EDGE
case .mirrored: GL_MIRRORED_REPEAT
case .repeating: GL_REPEAT
}
}
}

View File

@ -0,0 +1,796 @@
import OpenGL
struct OpenGLState
{
struct Capability: OptionSet
{
let rawValue: UInt32
static let alphaTest = Self(rawValue: 1 << 0)
static let blend = Self(rawValue: 1 << 1)
static let colourMaterial = Self(rawValue: 1 << 2)
static let colourSum = Self(rawValue: 1 << 3)
static let cullFace = Self(rawValue: 1 << 4)
static let depthTest = Self(rawValue: 1 << 5)
static let dither = Self(rawValue: 1 << 6)
static let fog = Self(rawValue: 1 << 7)
static let light0 = Self(rawValue: 1 << 8)
static let light1 = Self(rawValue: 1 << 9)
static let light2 = Self(rawValue: 1 << 10)
static let light3 = Self(rawValue: 1 << 11)
static let light4 = Self(rawValue: 1 << 12)
static let light5 = Self(rawValue: 1 << 13)
static let light6 = Self(rawValue: 1 << 14)
static let light7 = Self(rawValue: 1 << 15)
static let lighting = Self(rawValue: 1 << 16)
//static let lineSmooth = Self(rawValue: 1 << 17)
static let lineStipple = Self(rawValue: 1 << 18)
static let multiSample = Self(rawValue: 1 << 19)
//static let pointSmooth = Self(rawValue: 1 << 20)
static let scissorTest = Self(rawValue: 1 << 21)
static let stencilTest = Self(rawValue: 1 << 22)
//static let texture1D = Self(rawValue: 1 << 23)
static let texture2D = Self(rawValue: 1 << 24)
static let clipPlane0 = Self(rawValue: 1 << 25)
static let clipPlane1 = Self(rawValue: 1 << 26)
static let clipPlane2 = Self(rawValue: 1 << 27)
static let clipPlane3 = Self(rawValue: 1 << 28)
static let clipPlane4 = Self(rawValue: 1 << 29)
static let clipPlane5 = Self(rawValue: 1 << 30)
//static let colourLogicOp = Self(rawValue: 1 << 31)
static let sampleCoverage = Self(rawValue: 1 << 17)
static let alphaToOne = Self(rawValue: 1 << 20)
static let rescaleNormal = Self(rawValue: 1 << 23)
static let frameBufferSrgb = Self(rawValue: 1 << 31)
static let all: [Self] = [ .alphaTest, .blend, .colourMaterial, .colourSum, .cullFace, .depthTest, .dither,
.fog, .light0, .light1, .light2, .light3, .light4, .light5, .light6, .light7, .lighting, /*.lineSmooth,*/
.lineStipple, /*.pointSmooth,*/ .multiSample, .scissorTest, .stencilTest, /*.texture1D,*/ .texture2D,
.clipPlane0, .clipPlane1, .clipPlane2, .clipPlane3, .clipPlane4, .clipPlane5, /*.colourLogicOp,*/
.sampleCoverage, .alphaToOne, .rescaleNormal, .frameBufferSrgb ]
static let initial: Self = [ .dither, .multiSample ]
var glCap: GLenum
{
let capEnum = switch self
{
case .alphaTest: GL_ALPHA_TEST
case .blend: GL_BLEND
case .colourMaterial: GL_COLOR_MATERIAL
case .colourSum: GL_COLOR_SUM
case .cullFace: GL_CULL_FACE
case .depthTest: GL_DEPTH_TEST
case .dither: GL_DITHER
case .fog: GL_FOG
case .light0: GL_LIGHT0
case .light1: GL_LIGHT1
case .light2: GL_LIGHT2
case .light3: GL_LIGHT3
case .light4: GL_LIGHT4
case .light5: GL_LIGHT5
case .light6: GL_LIGHT6
case .light7: GL_LIGHT7
case .lighting: GL_LIGHTING
//case .lineSmooth: GL_LINE_SMOOTH
case .lineStipple: GL_LINE_STIPPLE
case .multiSample: GL_MULTISAMPLE
//case .pointSmooth: GL_POINT_SMOOTH
case .scissorTest: GL_SCISSOR_TEST
case .stencilTest: GL_STENCIL_TEST
//case .texture1D: GL_TEXTURE_1D
case .texture2D: GL_TEXTURE_2D
case .clipPlane0: GL_CLIP_PLANE0
case .clipPlane1: GL_CLIP_PLANE1
case .clipPlane2: GL_CLIP_PLANE2
case .clipPlane3: GL_CLIP_PLANE3
case .clipPlane4: GL_CLIP_PLANE4
case .clipPlane5: GL_CLIP_PLANE5
//case .colourLogicOp: GL_COLOR_LOGIC_OP
case .sampleCoverage: GL_SAMPLE_COVERAGE
case .alphaToOne: GL_SAMPLE_ALPHA_TO_ONE
case .rescaleNormal: GL_RESCALE_NORMAL
case .frameBufferSrgb: GL_FRAMEBUFFER_SRGB
default: fatalError()
}
return GLenum(capEnum)
}
}
private var _capabilities: Capability = .initial
private mutating func toggle(_ cap: Capability, _ en: Bool)
{
if en
{
if !_capabilities.contains(cap)
{
glEnable(cap.glCap)
_capabilities.insert(cap)
}
}
else
{
if _capabilities.contains(cap)
{
glDisable(cap.glCap)
_capabilities.remove(cap)
}
}
}
mutating func enable(_ caps: Capability)
{
if Capability.all.contains(caps)
{
toggle(caps, true)
}
else
{
for i in Capability.all
{
if i.intersection(caps) == i { toggle(i, true) }
}
}
}
mutating func disable(_ caps: Capability)
{
if Capability.all.contains(caps)
{
toggle(caps, false)
}
else
{
for i in Capability.all
{
if i.intersection(caps) == i { toggle(i, false) }
}
}
}
struct ClientState: OptionSet
{
let rawValue: UInt32
static let colour = Self(rawValue: 1 << 0)
static let edgeFlag = Self(rawValue: 1 << 1)
static let fogCoord = Self(rawValue: 1 << 2)
static let index = Self(rawValue: 1 << 3)
static let normal = Self(rawValue: 1 << 4)
static let secondaryColour = Self(rawValue: 1 << 5)
static let textureCoord = Self(rawValue: 1 << 6)
static let vertex = Self(rawValue: 1 << 7)
static let all: [Self] = [ .colour, .edgeFlag, .fogCoord, .index, .normal, .secondaryColour, .textureCoord, .vertex ]
static let initial: Self = []
var glCap: GLenum
{
let capEnum = switch self
{
case .colour: GL_COLOR_ARRAY
case .edgeFlag: GL_EDGE_FLAG_ARRAY
case .fogCoord: GL_FOG_COORD_ARRAY
case .index: GL_INDEX_ARRAY
case .normal: GL_NORMAL_ARRAY
case .secondaryColour: GL_SECONDARY_COLOR_ARRAY
case .textureCoord: GL_TEXTURE_COORD_ARRAY
case .vertex: GL_VERTEX_ARRAY
default: fatalError()
}
return GLenum(capEnum)
}
}
private var _clientState: ClientState = .initial
private mutating func toggleClientState(_ cap: ClientState, _ en: Bool)
{
if en
{
if !_clientState.contains(cap)
{
glEnableClientState(cap.glCap)
_clientState.insert(cap)
}
}
else
{
if _clientState.contains(cap)
{
glDisableClientState(cap.glCap)
_clientState.remove(cap)
}
}
}
mutating func enableClient(_ caps: ClientState)
{
if ClientState.all.contains(caps)
{
toggleClientState(caps, true)
}
else
{
for i in ClientState.all
{
if i.intersection(caps) == i { toggleClientState(i, true) }
}
}
}
mutating func disableClient(_ caps: ClientState)
{
if ClientState.all.contains(caps)
{
toggleClientState(caps, false)
}
else
{
for i in ClientState.all
{
if i.intersection(caps) == i { toggleClientState(i, false) }
}
}
}
private var _activeTexture2D = 0
private mutating func setActiveTexture2D(_ active: Int)
{
if active != _activeTexture2D
{
let slots = [ GL_TEXTURE0, GL_TEXTURE1, GL_TEXTURE2, GL_TEXTURE3, GL_TEXTURE4, GL_TEXTURE5, GL_TEXTURE6,
GL_TEXTURE7, GL_TEXTURE8, GL_TEXTURE9, GL_TEXTURE10, GL_TEXTURE11, GL_TEXTURE12, GL_TEXTURE13,
GL_TEXTURE14, GL_TEXTURE15, GL_TEXTURE16, GL_TEXTURE17, GL_TEXTURE18, GL_TEXTURE19, GL_TEXTURE20,
GL_TEXTURE21, GL_TEXTURE22, GL_TEXTURE23, GL_TEXTURE24, GL_TEXTURE25, GL_TEXTURE26, GL_TEXTURE27,
GL_TEXTURE28, GL_TEXTURE29, GL_TEXTURE30, GL_TEXTURE31 ]
glActiveTexture(GLenum(slots[active]))
_activeTexture2D = active
}
}
static let defaultTexture: UInt32 = 0
private var _texture2D: GLuint = GLuint(defaultTexture)
mutating func bindTexture2D(active: Int, texture: UInt32)
{
if GLuint(texture) != _texture2D
{
setActiveTexture2D(active)
glBindTexture(GLenum(GL_TEXTURE_2D), GLuint(texture))
_texture2D = GLuint(texture)
}
}
enum TexureParameter { case magFilter, minFilter, wrapS, wrapT }
mutating func texture2DParameter(active: Int, param: TexureParameter, int value: Int32)
{
setActiveTexture2D(active)
let pname = switch param
{
case .magFilter: GL_TEXTURE_MAG_FILTER
case .minFilter: GL_TEXTURE_MIN_FILTER
case .wrapS: GL_TEXTURE_WRAP_S
case .wrapT: GL_TEXTURE_WRAP_T
}
glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(pname), GLint(value))
}
private var _clearDepth: GLclampd = 1
var clearDepth: Double
{
get { Double(_clearDepth) }
set(newDepth)
{
if GLclampd(newDepth) != _clearDepth
{
glClearDepth(newDepth)
_clearDepth = GLclampd(newDepth)
}
}
}
private var _clearColour: Colour = .zero
mutating func clearColour(_ newColour: Colour)
{
if newColour != _clearColour
{
glClearColor(newColour.r, newColour.g, newColour.b, newColour.a)
_clearColour = newColour
}
}
enum CullFace { case front, back, frontAndBack }
private var _cullFace: CullFace = .back
var cullFace: CullFace
{
get { _cullFace }
set(newMode)
{
if newMode != _cullFace
{
let modeEnum = switch newMode
{
case .front: GL_FRONT
case .back: GL_BACK
case .frontAndBack: GL_FRONT_AND_BACK
}
glCullFace(GLenum(modeEnum))
_cullFace = newMode
}
}
}
enum DepthFunc { case never, less, equal, lessOrEqual, greater, notEqual, greaterOrEqual, always }
private var _depthFunc: DepthFunc = .less
var depthFunc: DepthFunc
{
get { _depthFunc }
set(newFunc)
{
if newFunc != _depthFunc
{
let funcEnum = switch newFunc
{
case .never: GL_NEVER
case .less: GL_LESS
case .equal: GL_EQUAL
case .lessOrEqual: GL_LEQUAL
case .greater: GL_GREATER
case .notEqual: GL_NOTEQUAL
case .greaterOrEqual: GL_GEQUAL
case .always: GL_ALWAYS
}
glDepthFunc(GLenum(funcEnum))
_depthFunc = newFunc
}
}
}
enum FogMode { case linear, exp, exp2 }
private var _fogMode: FogMode = .exp
var fogMode: FogMode
{
get { _fogMode }
set(newMode)
{
if newMode != _fogMode
{
let modeEnum = switch newMode
{
case .linear: GL_LINEAR
case .exp: GL_EXP
case .exp2: GL_EXP2
}
glFogi(GLenum(GL_FOG_MODE), modeEnum)
_fogMode = newMode
}
}
}
private var _fogDensity: Float = 1
var fogDensity: Float
{
get { _fogDensity }
set(newDensity)
{
if newDensity != _fogDensity
{
glFogf(GLenum(GL_FOG_DENSITY), newDensity)
_fogDensity = newDensity
}
}
}
private var _fogStart: Float = 0
var fogStart: Float
{
get { _fogStart }
set(newStart)
{
if newStart != _fogStart
{
glFogf(GLenum(GL_FOG_START), newStart)
_fogStart = newStart
}
}
}
private var _fogEnd: Float = 1
var fogEnd: Float
{
get { _fogEnd }
set(newEnd)
{
if newEnd != _fogEnd
{
glFogf(GLenum(GL_FOG_END), newEnd)
_fogEnd = newEnd
}
}
}
private var _fogColour: Colour = .zero
var fogColour: Colour
{
get { _fogColour }
set(newColour)
{
if newColour != _fogColour
{
withUnsafePointer(to: newColour)
{
$0.withMemoryRebound(to: GLfloat.self, capacity: 4)
{
glFogfv(GLenum(GL_FOG_COLOR), $0)
}
}
}
}
}
enum FogCoordinateSource { case coordinate, fragmentDepth }
private var _fogCoordinateSource: FogCoordinateSource = .fragmentDepth
var fogCoodinateSource: FogCoordinateSource
{
get { _fogCoordinateSource }
set(newCoordinateSource)
{
if newCoordinateSource != _fogCoordinateSource
{
let coordinateSourceEnum = switch newCoordinateSource
{
case .coordinate: GL_FOG_COORD
case .fragmentDepth: GL_FRAGMENT_DEPTH
}
glFogi(GLenum(GL_FOG_COORD_SRC), coordinateSourceEnum)
_fogCoordinateSource = newCoordinateSource
}
}
}
enum Hint
{
case fog
case generateMipmap
case lineSmooth
case perspectiveCorrection
case pointSmooth
case polygonSmooth
case textureCompression
}
enum HintBehaviour { case fastest, nicest, dontCare }
func setHint(target: Hint, hint: HintBehaviour)
{
let target = switch target
{
case .fog: GL_FOG_HINT
case .generateMipmap: GL_GENERATE_MIPMAP_HINT
case .lineSmooth: GL_LINE_SMOOTH_HINT
case .perspectiveCorrection: GL_PERSPECTIVE_CORRECTION_HINT
case .pointSmooth: GL_POINT_SMOOTH_HINT
case .polygonSmooth: GL_POLYGON_SMOOTH_HINT
case .textureCompression: GL_TEXTURE_COMPRESSION_HINT
}
let mode = switch hint
{
case .fastest: GL_FASTEST
case .nicest: GL_NICEST
case .dontCare: GL_DONT_CARE
}
glHint(GLenum(target), GLenum(mode))
}
struct LightAmbientProperty
{
private var _lightAmbient = [Colour](repeating: .black, count: 8)
subscript(index: Int) -> Colour
{
get { _lightAmbient[index] }
set(newAmbient)
{
if newAmbient != _lightAmbient[index]
{
withUnsafePointer(to: newAmbient)
{
$0.withMemoryRebound(to: GLfloat.self, capacity: 4)
{
// glLightModelfv(GLenum(GL_LIGHT_MODEL_AMBIENT), ambientColour)
glLightfv(GLenum(GL_LIGHT0 + Int32(index)), GLenum(GL_AMBIENT), $0)
}
}
_lightAmbient[index] = newAmbient
}
}
}
}
var lightAmbient = LightAmbientProperty()
struct LightDiffuseProperty
{
private var _lightDiffuse: [Colour] = [ .white, .black, .black, .black, .black, .black, .black, .black ]
subscript(index: Int) -> Colour
{
get { _lightDiffuse[index] }
set(newDiffuse)
{
if newDiffuse != _lightDiffuse[index]
{
withUnsafePointer(to: newDiffuse)
{
$0.withMemoryRebound(to: GLfloat.self, capacity: 4)
{
glLightfv(GLenum(GL_LIGHT0 + Int32(index)), GLenum(GL_DIFFUSE), $0)
}
}
_lightDiffuse[index] = newDiffuse
}
}
}
}
var lightDiffuse = LightDiffuseProperty()
struct LightSpecularProperty
{
private var _lightSpecular: [Colour] = [ .white, .black, .black, .black, .black, .black, .black, .black ]
subscript(index: Int) -> Colour
{
get { _lightSpecular[index] }
set(newSpecular)
{
if newSpecular != _lightSpecular[index]
{
withUnsafePointer(to: newSpecular)
{
$0.withMemoryRebound(to: GLfloat.self, capacity: 4)
{
glLightfv(GLenum(GL_LIGHT0 + Int32(index)), GLenum(GL_SPECULAR), $0)
}
}
_lightSpecular[index] = newSpecular
}
}
}
}
var lightSpecular = LightSpecularProperty()
struct LightPositionProperty
{
private var _lightPosition = [Vec4f](repeating: .Z, count: 8)
subscript(index: Int) -> Vec4f
{
get { _lightPosition[index] }
set(newPosition)
{
withUnsafePointer(to: newPosition)
{
$0.withMemoryRebound(to: GLfloat.self, capacity: 4)
{
glLightfv(GLenum(GL_LIGHT0 + Int32(index)), GLenum(GL_POSITION), $0)
}
}
_lightPosition[index] = newPosition
}
}
}
var lightPosition = LightPositionProperty()
//private var _lightSpotDirection = [Vec4f](repeating: .zero, count: 8)
//private var _lightSpotExponent = [Vec4f](repeating: .zero, count: 8)
//private var _lightSpotCutoff = [Vec4f](repeating: .zero, count: 8)
struct LightConstantAttenuationProperty
{
private var _lightConstantAttenuation = [Float](repeating: 1, count: 8)
subscript(index: Int) -> Float
{
get { _lightConstantAttenuation[index] }
set(newConstantAttenuation)
{
if newConstantAttenuation != _lightConstantAttenuation[index]
{
glLightf(GLenum(GL_LIGHT0 + Int32(index)), GLenum(GL_CONSTANT_ATTENUATION), newConstantAttenuation)
_lightConstantAttenuation[index] = newConstantAttenuation
}
}
}
}
var lightConstantAttenuation = LightConstantAttenuationProperty()
struct LightLinearAttenuationProperty
{
private var _lightLinearAttenuation = [Float](repeating: 0, count: 8)
subscript(index: Int) -> Float
{
get { _lightLinearAttenuation[index] }
set(newLinearAttenuation)
{
if newLinearAttenuation != _lightLinearAttenuation[index]
{
glLightf(GLenum(GL_LIGHT0 + Int32(index)), GLenum(GL_LINEAR_ATTENUATION), newLinearAttenuation)
_lightLinearAttenuation[index] = newLinearAttenuation
}
}
}
}
var lightLinearAttenuation = LightLinearAttenuationProperty()
struct LightQuadraticAttenuation
{
private var _lightQuadraticAttenuation = [Float](repeating: 0, count: 8)
subscript(index: Int) -> Float
{
get { _lightQuadraticAttenuation[index] }
set(newQuadraticAttenuation)
{
if newQuadraticAttenuation != _lightQuadraticAttenuation[index]
{
glLightf(GLenum(GL_LIGHT0 + Int32(index)), GLenum(GL_QUADRATIC_ATTENUATION), newQuadraticAttenuation)
_lightQuadraticAttenuation[index] = newQuadraticAttenuation
}
}
}
}
var lightQuadraticAttenuation = LightQuadraticAttenuation()
var _lightModelAmbient = Colour(r: 0.2, g: 0.2, b: 0.2, a: 1)
var lightModelAmbient: Colour
{
get { _lightModelAmbient }
set(newAmbient)
{
if newAmbient != _lightModelAmbient
{
withUnsafePointer(to: newAmbient)
{
$0.withMemoryRebound(to: GLfloat.self, capacity: 4)
{
glLightModelfv(GLenum(GL_LIGHT_MODEL_AMBIENT), $0)
}
}
_lightModelAmbient = newAmbient
}
}
}
enum LightModelColourControl { case single, separateSpecular }
var _lightModelColourControl: LightModelColourControl = .single
var lightModelColourControl: LightModelColourControl
{
get { _lightModelColourControl }
set(newControl)
{
if newControl != _lightModelColourControl
{
let param = switch newControl
{
case .single: GL_SINGLE_COLOR
case .separateSpecular: GL_SEPARATE_SPECULAR_COLOR
}
glLightModelf(GLenum(GL_LIGHT_MODEL_COLOR_CONTROL), GLfloat(param))
_lightModelColourControl = newControl
}
}
}
var _lightModelLocalViewer = false
var lightModelLocalViewer: Bool
{
get { _lightModelLocalViewer }
set(newMode)
{
if newMode != _lightModelLocalViewer
{
glLightModeli(GLenum(GL_LIGHT_MODEL_LOCAL_VIEWER), newMode ? 1 : 0)
_lightModelLocalViewer = newMode
}
}
}
var _lightModeTwoSide = false
var lightModeTwoSide: Bool
{
get { _lightModeTwoSide }
set(newMode)
{
if newMode != _lightModeTwoSide
{
glLightModeli(GLenum(GL_LIGHT_MODEL_TWO_SIDE), newMode ? 1 : 0)
_lightModeTwoSide = newMode
}
}
}
enum MatrixMode { case modelView, projection, texture, colour }
private var _matrixMode: MatrixMode = .modelView
var matrixMode: MatrixMode
{
get { _matrixMode }
set(newMode)
{
if newMode != _matrixMode
{
let modeEnum = switch newMode
{
case .modelView: GL_MODELVIEW
case .projection: GL_PROJECTION
case .texture: GL_TEXTURE
case .colour: GL_COLOR
}
glMatrixMode(GLenum(modeEnum))
_matrixMode = newMode
}
}
}
static let defaultBuffer: UInt32 = 0
private var _arrayBuffer = GLuint(defaultBuffer)
var arrayBuffer: UInt32
{
get { UInt32(_arrayBuffer) }
set(newBuf)
{
if newBuf != _arrayBuffer
{
_arrayBuffer = GLuint(newBuf)
glBindBuffer(GLenum(GL_ARRAY_BUFFER), _arrayBuffer)
}
}
}
private var _elementArrayBuffer = GLuint(defaultBuffer)
var elementArrayBuffer: UInt32
{
get { UInt32(_elementArrayBuffer) }
set(newBuf)
{
if newBuf != _elementArrayBuffer
{
_elementArrayBuffer = GLuint(newBuf)
glBindBuffer(GLenum(GL_ELEMENT_ARRAY_BUFFER), _elementArrayBuffer)
}
}
}
}

View File

@ -0,0 +1,134 @@
import Foundation
import OpenGL.GL
public protocol Renderer
{
func create(sdlWindow: OpaquePointer) throws
func delete()
func newFrame()
func resize(width: Int32, height: Int32)
var clearColour: Colour { get set }
func setVsync(mode: VSyncMode) throws
func createMesh(mesh: Mesh) throws -> RenderMesh
func createTexture(data: UnsafeRawPointer, width: Int, height: Int) throws -> RenderTexture2D
func createTexture(data: UnsafeRawPointer, width: Int, height: Int,
filter: FilterMode, mipMode: MipMode) throws -> RenderTexture2D
func deleteMesh(_ mesh: RenderMesh)
func deleteTexture(_ texture: RenderTexture2D)
func setProjection(matrix: Mat4f)
func setView(matrix: Mat4f)
func setMaterial(_ mat: Material)
func draw(mesh: RenderMesh, model: Mat4f, environment: Environment)
func draw(mesh: RenderMesh, environment: Environment)
func drawGizmos(lines: [Line])
}
public enum RendererError: Error
{
case sdlError(message: String)
}
public enum VSyncMode
{
case off
case on
case adaptive
}
public enum FilterMode
{
case point
case linear
}
public enum MipMode
{
case off
case nearest
case linear
}
public enum WrapMode
{
case clamping
case clampBorder
case clampEdge
case mirrored
case repeating
}
public protocol RendererResource
{
associatedtype T: Resource
static var empty: Self { get }
var isValid: Bool { get }
}
public struct RenderMesh: RendererResource
{
public typealias T = Mesh
public static var empty: RenderMesh { .init() }
public var isValid: Bool { _valid }
private let _valid: Bool;
let vboHnd: Int, iboHnd: Int
let subMeshes: [Mesh.SubMesh]
private init()
{
self._valid = false
self.vboHnd = 0
self.iboHnd = 0
self.subMeshes = []
}
init(vbo: Int, ibo: Int, subMeshes: [Mesh.SubMesh])
{
self._valid = true
self.vboHnd = vbo
self.iboHnd = ibo
self.subMeshes = subMeshes
}
}
public struct RenderTexture2D: RendererResource
{
public typealias T = Texture2D
public static var empty: Self { .init(0) }
public var isValid: Bool { id != 0 }
let id: UInt32
init(_ id: UInt32)
{
self.id = id
}
}
public struct Line
{
let from: Vec3f, to: Vec3f, colour: Colour
public init(from: Vec3f, to: Vec3f, colour: Colour)
{
self.from = from
self.to = to
self.colour = colour
}
}

View File

@ -0,0 +1,31 @@
import Foundation
public struct Texture2D: Resource
{
public let id: RenderTexture2D
public let width: Int, height: Int
}
extension Texture2D
{
public static let empty = Self(id: .empty, width: 0, height: 0)
}
public struct Texture2DParameters: ContentLoaderParametersProtocol
{
public typealias T = Texture2D
var minFilter: FilterMode, magFilter: FilterMode
var wrapMode: WrapMode
var mipMode: MipMode
public init(minFilter: FilterMode = .linear, magFilter: FilterMode = .linear, wrapMode: WrapMode = .repeating, mipMode: MipMode = .off)
{
self.minFilter = minFilter
self.magFilter = magFilter
self.wrapMode = wrapMode
self.mipMode = mipMode
}
}

View File

@ -0,0 +1,20 @@
import Foundation
extension FileHandle
{
// FixedWidthInteger or BinaryFloatingPoint
func read<T: FixedWidthInteger>(as: T.Type) throws -> T
{
let size = MemoryLayout<T>.size
guard let data = try self.read(upToCount: size)
else { throw NSError(domain: "FileHandle.read", code: NSFileReadUnknownError, userInfo: [
NSLocalizedFailureReasonErrorKey: "Read error",
NSLocalizedDescriptionKey: "Read error"]) }
guard data.count == size
else { throw NSError(domain: "FileHandle.read", code: NSFileReadUnknownError, userInfo: [
NSLocalizedFailureReasonErrorKey: "Value underread",
NSLocalizedDescriptionKey: "Value underread"]) }
return data.withUnsafeBytes { p -> T in p.load(as: T.self) }
}
}

View File

@ -0,0 +1,52 @@
import Foundation
class TextFile
{
private var _hnd: UnsafeMutablePointer<FILE>
private let _maxLength: Int
init(fileURL: URL, maxLineLength: Int = 1024) throws
{
guard let f = fopen(fileURL.path, "r")
else { throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno)) }
_hnd = f
_maxLength = maxLineLength
}
deinit
{
fclose(_hnd)
}
struct LinesSequence: Sequence, IteratorProtocol
{
typealias Element = String
private var _hnd: UnsafeMutablePointer<FILE>
private var _buffer: [CChar]
fileprivate init(_ handle: UnsafeMutablePointer<FILE>, _ maxLength: Int)
{
_hnd = handle
_buffer = [CChar](repeating: 0, count: maxLength)
}
mutating func next() -> String?
{
guard fgets(&_buffer, Int32(_buffer.count), _hnd) != nil
else
{
if feof(_hnd) != 0 { return nil }
else { return nil }
}
let length = strcspn(_buffer, "\r\n")
_buffer[length] = "\0".utf8CString[0]
//if let pos = strchr(_buffer, Int32("\n".utf8CString[0])) { pos[0] = "\0".utf8CString[0] }
return String(cString: _buffer)
}
}
public var lines: LinesSequence { LinesSequence(_hnd, _maxLength) }
}

View File

@ -0,0 +1,248 @@
import Foundation
struct UBJsonReader
{
private var file: FileHandle
init(file: FileHandle)
{
self.file = file
}
func read() throws -> UBJsonToken
{
guard try readCharacter() == "{"
else { throw UBReaderError.badFormat("Stream does not start with an object") }
return try readObject()
}
private func parse(type: Character) throws -> UBJsonToken
{
let oldFormat = true
return switch type
{
case "{": try readObject()
case "[": try readArray()
case "Z": .null
case "N": .noop
case "T": .bool(true)
case "F": .bool(false)
case "i": oldFormat
? .int16(try file.read(as: Int16.self).bigEndian)
: .int8(try file.read(as: Int8.self))
case "U": .uint8(try file.read(as: UInt8.self))
case "I": oldFormat
? .int32(try file.read(as: Int32.self).bigEndian)
: .int16(try file.read(as: Int16.self).bigEndian)
case "l": .int32(try file.read(as: Int32.self).bigEndian)
case "L": .int64(try file.read(as: Int64.self).bigEndian)
case "d": .float32(try readBeFloatPiss())
case "D": .float64(try readBeFloat())
case "H": throw UBReaderError.badFormat("High-precision numbers are unsupported")
case "C": .char(try readCharacter())
case "S": .string(try readString())
default: throw UBReaderError.badToken("Unexpected token \"\(type)\"")
}
}
private func readObject() throws -> UBJsonToken
{
var items = Dictionary<String, UBJsonToken>()
while true
{
let type = try readCharacter()
var name: String
switch type
{
case "S", "i":
name = try readString(type)
case "}":
return .object(items)
default:
throw UBReaderError.badToken("Unexpected token while reading object field key")
}
if items.keys.contains(name) { throw UBReaderError.badFormat("Object contains overlapping keys") }
items[name] = try parse(type: try readCharacter())
}
}
private func readArray() throws -> UBJsonToken
{
var array = [UBJsonToken]()
while true
{
let type = try readCharacter()
switch type
{
case "]":
return .array(array)
default:
array.append(try parse(type: type))
}
}
}
private func readBeFloatPiss() throws -> Float
{
guard var bytes = try? file.read(upToCount: 4)
else { throw UBReaderError.readError("Read failure while reading float data") }
return Float(bitPattern: bytes.withUnsafeBytes { $0.load(as: UInt32.self) }.bigEndian)
}
private func readBeFloat<T: BinaryFloatingPoint, U: UnsignedInteger>() throws -> T where T.RawSignificand == U
{
guard var bytes = try? file.read(upToCount: MemoryLayout<U>.size)
else { throw UBReaderError.readError("Read failure while reading float data") }
bytes.reverse()
return T(bytes.withUnsafeBytes { $0.load(as: U.self) })
}
private func readCharacter() throws -> Character
{
guard let raw = try? file.read(as: UInt8.self),
let uni = UnicodeScalar(Int(raw))
else { throw UBReaderError.readError("Read failure while reading character") }
return Character(uni)
}
private func readString(_ optType: Character? = nil) throws -> String
{
let type = optType == nil ? try readCharacter() : optType!
var length: Int
switch type
{
case "S":
guard try readCharacter() == "i"
else { throw UBReaderError.badToken("Malformed string") }
fallthrough
case "i":
length = Int(try file.read(as: Int8.self))
case "s":
length = Int(try file.read(as: UInt8.self))
default: throw UBReaderError.badToken("Unexpected token while reading string")
}
if length < 0 { throw UBReaderError.badToken("Negative string length") }
if length == 0 { return "" }
guard let data = try file.read(upToCount: length)
else { throw UBReaderError.readError("Error reading string") }
return String(decoding: data, as: UTF8.self)
}
}
enum UBReaderError: Error
{
case badFormat(_ message: String)
case badToken(_ message: String)
case readError(_ message: String)
case valueError(_ message: String)
}
enum UBJsonToken
{
case object(_ fields: Dictionary<String, UBJsonToken>)
case array(_ items: [UBJsonToken])
case null
case noop
case bool(_ value: Bool)
case int8(_ value: Int8)
case uint8(_ value: UInt8)
case int16(_ value: Int16)
case int32(_ value: Int32)
case int64(_ value: Int64)
case highPrecision
case float32(_ value: Float32)
case float64(_ value: Float64)
case char(_ value: Character)
case string(_ value: String)
}
extension UBJsonToken
{
var array: [UBJsonToken]
{
get throws
{
if case .array(let items) = self { return items }
throw UBReaderError.valueError("Not an array")
}
}
func getArray(key: String) throws -> [UBJsonToken]
{
if case .object(let fields) = self
{
guard let child = fields[key]
else { throw UBReaderError.valueError("Field \"\(key)\" not found") }
return try child.array
}
throw UBReaderError.valueError("Not an object")
}
var int16: Int16
{
get throws
{
if case .int16(let value) = self { return value }
throw UBReaderError.valueError("Not an int16")
}
}
func getInt16Array(key: String) throws -> [Int16]
{
try getArray(key: key).map(
{ i in
if case .int8(let value) = i { return Int16(value) }
else if case .uint8(let value) = i { return Int16(value) }
else if case .int16(let value) = i { return value }
else if case .int32(let value) = i
{
if value < Int16.min || value > Int16.max { throw UBReaderError.valueError("Value out of range") }
return Int16(truncatingIfNeeded: value)
}
else if case .int64(let value) = i
{
if value < Int16.min || value > Int16.max { throw UBReaderError.valueError("Value out of range") }
return Int16(truncatingIfNeeded: value)
}
else { throw UBReaderError.valueError("Can't read array as int16s") }
})
}
var float: Float
{
get throws
{
if case .float32(let value) = self { return value }
throw UBReaderError.valueError("Not a float32")
}
}
func getFloatArray(key: String) throws -> [Float]
{
try getArray(key: key).map({ try $0.float })
}
var string: String
{
get throws
{
if case .string(let value) = self { return value }
throw UBReaderError.valueError("Not a string")
}
}
func getString(key: String, default defStr: String? = nil) throws -> String
{
if case .object(let fields) = self
{
guard let child = fields[key] else
{
if defStr == nil { throw UBReaderError.valueError("Field \"\(key)\" not found") }
return defStr!
}
return try child.string
}
throw UBReaderError.valueError("Not an object")
}
}

View File

@ -0,0 +1,276 @@
import Foundation
import simd
import JolkEngine
@main struct Program
{
static func main()
{
Application(
application: CavesOfJolk(),
config: ApplicationConfiguration(
resizable: true,
vSync: .on,
windowWidth: 1280,
windowHeight: 720,
bundle: Bundle.module)
).run()
}
}
class CavesOfJolk: ApplicationImplementation
{
private var aspect: Float = 0
private var drawEdges = false
var env = Environment()
var worldMesh = RenderMesh.empty, cube = RenderMesh.empty, suzanne = RenderMesh.empty, toybox = RenderMesh.empty
var texture = Texture2D.empty, jolkTex = Texture2D.empty, suzanneDiffuse = Texture2D.empty
private lazy var world = Collision()
private lazy var colin = Colin()
private lazy var jolkCube = JolkCube(position: .init(0, 1, -4))
private var lightTheta: Float = 0
private var frameCount = 0
private var frameTimer: Float = 1
func create(render: inout Renderer)
{
render.clearColour = XnaColour.CornflowerBlue
env.setFog(
colour: XnaColour.CornflowerBlue,
//mode: .depth, falloff: .range(type: .linear, start: 4, end: 38))
//mode: .depth, falloff: .range(type: .linear, start: 1.25, end: 24.5))
//mode: .distance, falloff: .factor(type: .exp2, density: 0.1))
mode: .depth, falloff: .factor(type: .exp2, density: 0.0222))
env.addAmbientLight(colour: XnaColour.DarkSlateGray.lighten(by: -0.666))
env.addDirectionalLight(
direction: -Vec3f(1, -1, -1).normalised,
colour: XnaColour.BlanchedAlmond
.lighten(by: -0.02)
.mix(with: XnaColour.LightSlateGray, 0.5)
.mix(with: XnaColour.White, 0.125))
env.addPointLight(
position: Vec3f(3, 0.33, -5),
colour: XnaColour.Green.mix(with: XnaColour.Gray, 0.5),
intensity: 2)
env.addPointLight(
position: Vec3f(5.5, 0.33, -6),
colour: XnaColour.Red.mix(with: XnaColour.Gray, 0.5),
intensity: 2)
env.addPointLight(
position: Vec3f(4, 0.33, -7),
colour: XnaColour.Blue.mix(with: XnaColour.Gray, 0.5),
intensity: 2)
}
func loadContent(content: inout ContentManager) throws
{
try loadWorld(&content)
cube = try content.create(mesh: .init(
vertices: [
.init(position: Vec3f(-1, -1, 1), normal: .forward, texCoord: Vec2f(0, 0)),
.init(position: Vec3f( 1, -1, 1), normal: .forward, texCoord: Vec2f(1, 0)),
.init(position: Vec3f(-1, 1, 1), normal: .forward, texCoord: Vec2f(0, 1)),
.init(position: Vec3f( 1, 1, 1), normal: .forward, texCoord: Vec2f(1, 1)),
.init(position: Vec3f( 1, -1, 1), normal: .right, texCoord: Vec2f(0, 0)),
.init(position: Vec3f( 1, -1, -1), normal: .right, texCoord: Vec2f(1, 0)),
.init(position: Vec3f( 1, 1, 1), normal: .right, texCoord: Vec2f(0, 1)),
.init(position: Vec3f( 1, 1, -1), normal: .right, texCoord: Vec2f(1, 1)),
.init(position: Vec3f( 1, -1, -1), normal: .back, texCoord: Vec2f(0, 0)),
.init(position: Vec3f(-1, -1, -1), normal: .back, texCoord: Vec2f(1, 0)),
.init(position: Vec3f( 1, 1, -1), normal: .back, texCoord: Vec2f(0, 1)),
.init(position: Vec3f(-1, 1, -1), normal: .back, texCoord: Vec2f(1, 1)),
.init(position: Vec3f(-1, -1, -1), normal: .left, texCoord: Vec2f(0, 0)),
.init(position: Vec3f(-1, -1, 1), normal: .left, texCoord: Vec2f(1, 0)),
.init(position: Vec3f(-1, 1, -1), normal: .left, texCoord: Vec2f(0, 1)),
.init(position: Vec3f(-1, 1, 1), normal: .left, texCoord: Vec2f(1, 1)),
.init(position: Vec3f(-1, -1, -1), normal: .down, texCoord: Vec2f(0, 0)),
.init(position: Vec3f( 1, -1, -1), normal: .down, texCoord: Vec2f(1, 0)),
.init(position: Vec3f(-1, -1, 1), normal: .down, texCoord: Vec2f(0, 1)),
.init(position: Vec3f( 1, -1, 1), normal: .down, texCoord: Vec2f(1, 1)),
.init(position: Vec3f(-1, 1, 1), normal: .up, texCoord: Vec2f(0, 0)),
.init(position: Vec3f( 1, 1, 1), normal: .up, texCoord: Vec2f(1, 0)),
.init(position: Vec3f(-1, 1, -1), normal: .up, texCoord: Vec2f(0, 1)),
.init(position: Vec3f( 1, 1, -1), normal: .up, texCoord: Vec2f(1, 1))
],
indices: [
0, 1, 2, 2, 1, 3,
4, 5, 6, 6, 5, 7,
8, 9, 10, 10, 9, 11,
12, 13, 14, 14, 13, 15,
16, 17, 18, 18, 17, 19,
20, 21, 22, 22, 21, 23 ]))
suzanne = try content.load("suzanne.g3db")
toybox = try content.load("toybox.g3db")
let linearMipped = Texture2DParameters(
minFilter: .linear, magFilter: .linear,
wrapMode: .repeating,
mipMode: .linear)
let linearClamp = Texture2DParameters(
minFilter: .linear, magFilter: .linear,
wrapMode: .clampEdge)
texture = try content.load("cobblestone.png", params: linearMipped)
jolkTex = try content.load("jolkmeup.jpg", params: linearClamp)
suzanneDiffuse = try content.load("suzanne_diffuse.png", params: linearMipped)
}
func resize(width: Int, height: Int)
{
aspect = Float(width) / Float(height)
print(aspect)
}
func update(deltaTime: Float)
{
colin.update(deltaTime: deltaTime, world: world)
jolkCube.update(deltaTime: deltaTime, world: world)
lightTheta += deltaTime
frameCount += 1
frameTimer -= deltaTime
if frameTimer <= 0
{
print("FPS: \(frameCount)")
frameCount = 0
frameTimer += 1.0
}
}
private func loadWorld(_ content: inout ContentManager) throws
{
//let world: Mesh = try content.load("World.obj")
let obj = try ObjReader.read(url: try content.getResource("World.obj"))
let mesh: Mesh = try ObjLoader.read(model: obj)
worldMesh = try content.create(mesh: mesh)
if let collision = obj.objects["Collision3D"]
{
world.build(obj: obj, collision: collision)
}
}
/*
if let collision = world.subMeshes["Collision"]
{
edges.reserveCapacity(collision.length / 6)
let get2dVertex =
{ i in
let vertex = world.vertices[Int(world.indices[i])]
let position = Vec2f(vertex.position.x, vertex.position.z)
let normal = Vec2f(vertex.normal.x, vertex.normal.z)
return (position, normal)
}
for i in stride(from: collision.start, to: collision.start + collision.length, by: 3)
{
let (v0p, v0n) = get2dVertex(i)
let (v1p, v1n) = get2dVertex(i + 1)
let (v2p, v2n) = get2dVertex(i + 2)
let (p0, p1) = if v0p == v2p || v1p == v2p { (v0p, v1p) }
else if v0p == v1p { (v0p, v2p) }
else { throw NSError() }
let edge = Edge(p: p0.lerp(p1, 0.5), n: (v0n + v1n + v2n).normalised, w: (p0 - p1).len)
if !edges.contains(where: { edge.p == $0.p && edge.w == $0.w })
{
edges.append(edge)
}
}
}
*/
func draw(render: inout Renderer, deltaTime: Float)
{
// Setup camera
render.setProjection(matrix:
.perspective(fovY: Float.rad(fromDeg: colin.fov), aspect: aspect, zNear: 0.1, zFar: 100))
render.setView(matrix: colin.transform)
// Update point lights
var theta = Vec3f(repeating: lightTheta) * Vec3f(0.12, 0.011, 0.056) * 6
var i = env.lights.startIndex
while i != env.lights.endIndex
{
if case .point(let colour, _, _) = env.lights[i]
{
env.lights[i] = .point(
colour: colour,
position: Vec3f(
4 + 6 * cos(theta[0]),
0.33 + 0.33 * sin(theta[1]),
-6 + 3 * sin(theta[0] * 2)),
intensity: 3.1 + 1.53 * cos(theta[2]))
let spacing = 0.5 * Float.pi * (2 / 3.0)
theta += spacing * Vec3f(1, 0.98, 0.5566)
}
i = env.lights.index(after: i)
}
// Draw world
render.setMaterial(Material(
specular: XnaColour.BlanchedAlmond.mix(with: .black, 0.12),
texture: texture.id))
render.draw(mesh: worldMesh, environment: env)
// Draw jolked up shit
render.setMaterial(Material(
specular: XnaColour.Gray,
gloss: 20.0,
texture: jolkTex.id))
render.draw(mesh: cube, model: jolkCube.transform, environment: env)
render.setMaterial(Material(texture: suzanneDiffuse.id))
render.draw(mesh: suzanne, model: .translate(.up + Vec3f(3.0, 0.0, -3.5) * 2.5), environment: env)
render.draw(mesh: toybox,
model: .translate(Vec3f(6.0, 0.667, -3.5) * Vec3f(2.5, 1, 2.5))
* .rotate(y: lightTheta * 0.5) * .scale(scalar: 0.25), environment: env)
if Input.instance.keyPressed(.c) { drawEdges = !drawEdges }
if drawEdges
{
/*
var lines = [Line](
repeating: .init(from: .init(), to: .init(), colour: .zero),
count: edges.count * 3)
var i: Int = 0
for edge in edges
{
let tangent = Vec2f(edge.n.y, -edge.n.x)
let a = edge.p - tangent * edge.w * 0.5
let b = edge.p + tangent * edge.w * 0.5
lines[i] = Line(
from: Vec3f(a.x, 0, a.y),
to: Vec3f(b.x, 0, b.y),
colour: Colour(r: 0.1, g: 0.9, b: 0.1))
lines[i + 1] = Line(
from: Vec3f(edge.p.x, 0, edge.p.y),
to: Vec3f(edge.p.x + edge.n.x * 0.25, 0, edge.p.y + edge.n.y * 0.25),
colour: Colour(r: 0.9, g: 0.1, b: 0.1))
let deltaPos = Vec2f(colin.position.x, colin.position.z) - edge.p
let something = tangent * deltaPos.cross(edge.n)
lines[i + 2] = Line(
from: Vec3f(edge.p.x + something.x, 0.0, edge.p.y + something.y),
to: Vec3f(edge.p.x + something.x + edge.n.x * 0.5, 0.0, edge.p.y + something.y + edge.n.y * 0.5),
colour: XnaColour.Azure)
i += 3
}
render.drawGizmos(lines: lines)
*/
world.draw(render, position: colin.position)
}
}
}

View File

@ -0,0 +1,497 @@
import Foundation
import JolkEngine
class Collision
{
struct Edge { let p: Vec2f, n: Vec2f, w: Float }
enum Edge3D
{
case triangle(n: Vec3f, p: Vec3f, v: (Vec3f, Vec3f, Vec3f))
case aabbFloor(n: Vec3f, p: Vec3f, w: Float, d: Float)
case quad(p: (Vec3f, Vec3f, Vec3f, Vec3f), w: Winding)
}
var edge3d = [Edge3D]()
/*
0,-2 1,-2
*--*
| |
0,-1 * * 1,-1
| |
*--*
0, 0 1, 0
*/
init()
{
assert(Self.isRectangle([
Vec3f(0.0, 0.0, 0.0),
Vec3f(0.0, 0.0, -1.0),
Vec3f(1.0, 0.0, -1.0),
Vec3f(1.0, 0.0, 0.0),
]))
// CW
assert(Self.isRectangle([
Vec3f(0.0, 0.0, 0.0),
Vec3f(0.0, 0.0, -1.0),
Vec3f(0.0, 0.0, -2.0),
Vec3f(1.0, 0.0, -2.0),
Vec3f(1.0, 0.0, -1.0),
Vec3f(1.0, 0.0, 0.0),
]))
assert(Self.isRectangle([
Vec3f(0.0, 0.0, -1.0),
Vec3f(0.0, 0.0, -2.0),
Vec3f(1.0, 0.0, -2.0),
Vec3f(1.0, 0.0, -1.0),
Vec3f(1.0, 0.0, 0.0),
Vec3f(0.0, 0.0, 0.0),
]))
assert(Self.isRectangle([
Vec3f(0.0, 0.0, -2.0),
Vec3f(1.0, 0.0, -2.0),
Vec3f(1.0, 0.0, -1.0),
Vec3f(1.0, 0.0, 0.0),
Vec3f(0.0, 0.0, 0.0),
Vec3f(0.0, 0.0, -1.0),
]))
assert(Self.isRectangle([
Vec3f(1.0, 0.0, -2.0),
Vec3f(1.0, 0.0, -1.0),
Vec3f(1.0, 0.0, 0.0),
Vec3f(0.0, 0.0, 0.0),
Vec3f(0.0, 0.0, -1.0),
Vec3f(0.0, 0.0, -2.0),
]))
assert(Self.isRectangle([
Vec3f(1.0, 0.0, -1.0),
Vec3f(1.0, 0.0, 0.0),
Vec3f(0.0, 0.0, 0.0),
Vec3f(0.0, 0.0, -1.0),
Vec3f(0.0, 0.0, -2.0),
Vec3f(1.0, 0.0, -2.0),
]))
assert(Self.isRectangle([
Vec3f(1.0, 0.0, 0.0),
Vec3f(0.0, 0.0, 0.0),
Vec3f(0.0, 0.0, -1.0),
Vec3f(0.0, 0.0, -2.0),
Vec3f(1.0, 0.0, -2.0),
Vec3f(1.0, 0.0, -1.0),
]))
// CCW
assert(Self.isRectangle([
Vec3f( 0.0, 0.0, 0.0),
Vec3f( 0.0, 0.0, -1.0),
Vec3f( 0.0, 0.0, -2.0),
Vec3f(-1.0, 0.0, -2.0),
Vec3f(-1.0, 0.0, -1.0),
Vec3f(-1.0, 0.0, 0.0),
]))
assert(Self.isRectangle([
Vec3f( 0.0, 0.0, -1.0),
Vec3f( 0.0, 0.0, -2.0),
Vec3f(-1.0, 0.0, -2.0),
Vec3f(-1.0, 0.0, -1.0),
Vec3f(-1.0, 0.0, 0.0),
Vec3f( 0.0, 0.0, 0.0),
]))
assert(Self.isRectangle([
Vec3f( 0.0, 0.0, -2.0),
Vec3f(-1.0, 0.0, -2.0),
Vec3f(-1.0, 0.0, -1.0),
Vec3f(-1.0, 0.0, 0.0),
Vec3f( 0.0, 0.0, 0.0),
Vec3f( 0.0, 0.0, -1.0),
]))
assert(Self.isRectangle([
Vec3f(-1.0, 0.0, -2.0),
Vec3f(-1.0, 0.0, -1.0),
Vec3f(-1.0, 0.0, 0.0),
Vec3f( 0.0, 0.0, 0.0),
Vec3f( 0.0, 0.0, -1.0),
Vec3f( 0.0, 0.0, -2.0),
]))
assert(Self.isRectangle([
Vec3f(-1.0, 0.0, -1.0),
Vec3f(-1.0, 0.0, 0.0),
Vec3f( 0.0, 0.0, 0.0),
Vec3f( 0.0, 0.0, -1.0),
Vec3f( 0.0, 0.0, -2.0),
Vec3f(-1.0, 0.0, -2.0),
]))
assert(Self.isRectangle([
Vec3f(-1.0, 0.0, 0.0),
Vec3f( 0.0, 0.0, 0.0),
Vec3f( 0.0, 0.0, -1.0),
Vec3f( 0.0, 0.0, -2.0),
Vec3f(-1.0, 0.0, -2.0),
Vec3f(-1.0, 0.0, -1.0),
]))
}
private static let epsilon: Float = 0.00001 //.ulpOfOne
private static func isSimpleQuad(_ p: (Vec3f, Vec3f, Vec3f, Vec3f)) -> Bool
{
if abs(p.0.y - p.1.y) <= epsilon { return abs(p.2.y - p.3.y) <= epsilon }
if abs(p.1.y - p.2.y) <= epsilon { return abs(p.3.y - p.0.y) <= epsilon }
return false
}
private static func isRectangle(_ p: (Vec3f, Vec3f, Vec3f, Vec3f)) -> Bool
{
if abs(p.0.x - p.1.x) <= epsilon &&
abs(p.1.z - p.2.z) <= epsilon &&
abs(p.2.x - p.3.x) <= epsilon &&
abs(p.3.z - p.0.z) <= epsilon { return true }
if abs(p.0.z - p.1.z) <= epsilon &&
abs(p.1.x - p.2.x) <= epsilon &&
abs(p.2.z - p.3.z) <= epsilon &&
abs(p.3.x - p.0.x) <= epsilon { return true }
return false
}
private static func isRectangle(_ positions: [Vec3f]) -> Bool
{
var winding: Winding = .none
var xdir: Float = 0.0, zdir: Float = 0.0
let first = positions[0]
var previous = first
for p in positions[1...]
{
let (xdelta, zdelta) = (p.x - previous.x, p.z - previous.z)
let (xzero, zzero) = (abs(xdelta) <= epsilon, abs(zdelta) <= epsilon)
if !xzero && zzero
{
if xdir != 0.0 && xdelta.sign != xdir.sign { return false }
if zdir != 0.0
{
switch winding
{
case .none: winding = xdelta.sign == zdir.sign ? .ccw : .cw
case .cw: if xdelta.sign == zdir.sign { return false }
case .ccw: if xdelta.sign != zdir.sign { return false }
}
}
(xdir, zdir) = (xdelta, 0.0)
}
else if xzero && !zzero
{
if zdir != 0.0 && zdelta.sign != zdir.sign { return false }
if xdir != 0.0
{
switch winding
{
case .none: winding = zdelta.sign == xdir.sign ? .cw : .ccw
case .cw: if zdelta.sign != xdir.sign { return false }
case .ccw: if zdelta.sign == xdir.sign { return false }
}
}
(xdir, zdir) = (0.0, zdelta)
}
else if !xzero && !zzero { return false }
previous = p
}
return abs(first.x - previous.x) <= epsilon || abs(first.z - previous.z) <= epsilon
}
private static func getQuadWinding(_ p: (Vec3f, Vec3f, Vec3f, Vec3f)) -> Winding
{
var area: Float = 0.0
area += (p.1.x - p.0.x) * ((p.0.z + p.1.z) * 0.5)
area += (p.2.x - p.1.x) * ((p.1.z + p.2.z) * 0.5)
area += (p.3.x - p.2.x) * ((p.2.z + p.3.z) * 0.5)
area += (p.0.x - p.3.x) * ((p.3.z + p.0.z) * 0.5)
return area.sign == .plus ? .ccw : .cw // z is towards us
}
static func quadSpaceFromCartesian(quad: (Vec3f, Vec3f, Vec3f, Vec3f), position: Vec3f) -> Vec2f
{
let p = (
Vec2d(Double(quad.0.x), Double(quad.0.z)),
Vec2d(Double(quad.1.x), Double(quad.1.z)),
Vec2d(Double(quad.2.x), Double(quad.2.z)),
Vec2d(Double(quad.3.x), Double(quad.3.z)))
let xz = Vec2d(Double(position.x), Double(position.z))
/*
let old = {
let a = xz.x - p.0.x
let b = p.1.x - p.0.x
let c = p.3.x - p.0.x
let d = p.0.x - p.1.x + p.2.x - p.3.x
let f = xz.y - p.0.y
let g = p.1.y - p.0.y
let h = p.3.y - p.0.y
let j = p.0.y - p.1.y + p.2.y - p.3.y
let v2 = -c * j - (-d * h)
let v1 = a * j - c * g - (d * f - b * h)
let v0 = a * g - b * f
let vq = (-v1 + sqrt(v1 * v1 - 4.0 * v2 * v0)) / (2.0 * v2)
let uq = (a - c * vq) / (b + d * vq)
return Vec2f(Float(uq), Float(vq))
}
*/
let a = xz - p.0
let b = p.1 - p.0
let c = p.3 - p.0
let d = p.0 - p.1 + p.2 - p.3
let v0 = a.cross(b), v2 = c.cross(-d)
let v1 = a.x * d.y - b.y * c.x - (a.y * d.x - b.x * c.y)
let vq = (-v1 + sqrt(v1 * v1 - 4.0 * v2 * v0)) / (2.0 * v2)
let uq = (a.x - c.x * vq) / (b.x + d.x * vq)
return Vec2f(Float(uq), Float(vq))
//let oldUv = old()
//if !oldUv.x.isNaN || !uv.x.isNaN { assert (oldUv.x == uv.x) }
//if !oldUv.y.isNaN || !uv.y.isNaN { assert (oldUv.y == uv.y) }
//return uv
}
enum Winding { case none, cw, ccw }
func build(obj: ObjModel, collision: ObjModel.Object)
{
for face in collision.faces
{
switch face
{
case .triangle(let v1, let v2, let v3):
let p = (obj.positions[v1.p], obj.positions[v2.p], obj.positions[v3.p])
let n = (obj.normals[v1.n] + obj.normals[v2.n] + obj.normals[v3.n]).normalised
if abs(n.y) < 0.25 { continue }
edge3d.append(.triangle(n: n, p: (p.0 + p.1 + p.2) / 3.0, v: p))
case .quad(let v1, let v2, let v3, let v4):
let p = (obj.positions[v1.p], obj.positions[v2.p], obj.positions[v3.p], obj.positions[v4.p])
let n = (obj.normals[v1.n] + obj.normals[v2.n] + obj.normals[v3.n] + obj.normals[v4.n]).normalised
if Self.isSimpleQuad(p) && Self.isRectangle(p)
{
if abs(n.y) < 0.25 { continue }
let left = min(p.0.x, p.1.x, p.2.x, p.3.x)
let right = max(p.0.x, p.1.x, p.2.x, p.3.x)
let back = min(p.0.z, p.1.z, p.2.z, p.3.z)
let forward = max(p.0.z, p.1.z, p.2.z, p.3.z)
edge3d.append(.aabbFloor(n: n,
p: (p.0 + p.1 + p.2 + p.3) / 4.0,
w: (right - left) / 2.0,
d: (forward - back) / 2.0))
}
else
{
if abs(n.y) < 0.25 { continue }
edge3d.append(.quad(p: p, w: Self.getQuadWinding(p)))
/*
edge3d.append(.triangle(n: n, p: (p.0 + p.1 + p.2) / 3.0, v: (p.0, p.1, p.2)))
edge3d.append(.triangle(n: n, p: (p.2 + p.3 + p.0) / 3.0, v: (p.2, p.3, p.0)))
*/
}
case .ngon(let v):
let p = v.map { obj.positions[$0.p] }
let n = v.reduce(.zero) { $0 + obj.normals[$1.n] }.normalised
if abs(n.y) < 0.25 { continue }
if Self.isRectangle(p)
{
let left = p.map { $0.x }.min()!, right = p.map { $0.x }.max()!
let bottom = p.map { $0.y }.min()!, top = p.map { $0.y }.max()!
let back = p.map { $0.z }.min()!, forward = p.map { $0.z }.max()!
let position = Vec3f(left + right, bottom + top, back + forward) / 2.0
edge3d.append(.aabbFloor(n: n, p: position,
w: (right - left) / 2.0,
d: (forward - back) / 2.0))
}
else
{
let p0 = p[0]
for i in 1..<(v.count-1)
{
let p1 = p[i], p2 = p[i + 1]
edge3d.append(.triangle(n: n, p: (p0 + p1 + p2) / 3.0, v: (p0, p1, p2)))
}
}
default:
continue
}
}
}
func draw(_ render: Renderer, position: Vec3f)
{
var lines = [Line](
repeating: .init(from: .init(), to: .init(), colour: .zero),
count: edge3d.count * 6)
var i: Int = 0
for edge in edge3d
{
var o: Vec3f = .zero, n: Vec3f = .zero
switch edge
{
case .triangle(let trin, let trip, let triv):
o = trip
n = trin
let v0 = triv.1 - triv.0;
let v1 = triv.2 - triv.0;
let v2 = position - triv.0;
let iden = 1.0 / (v0.x * v1.z - v1.x * v0.z);
let v = (v2.x * v1.z - v1.x * v2.z) * iden;
let w = (v0.x * v2.z - v2.x * v0.z) * iden;
let colour = if v >= 0.0 && w >= 0.0 && v + w <= 1.0
{ XnaColour.Red } else { XnaColour.GreenYellow }
//let p = triv
//let det = (p.1.x - p.0.x) * (p.2.z - p.0.z) - (p.1.z - p.0.z) * (p.2.x - p.0.x)
//let colour: Colour = if
// det * (p.1.x - p.0.x) * (position.z - p.0.z) - (p.1.z - p.0.z) * (position.x - p.0.x) >= 0,
// det * (p.2.x - p.1.x) * (position.z - p.1.z) - (p.2.z - p.1.z) * (position.x - p.1.x) >= 0,
// det * (p.0.x - p.2.x) * (position.z - p.2.z) - (p.0.z - p.2.z) * (position.x - p.2.x) >= 0
/*
let side = { (v1: Vec3f, v2: Vec3f, p: Vec3f) in
(v2.z - v1.z) * (p.x - v1.x) + (v2.x + v1.x) * (position.z - v1.z) }
let colour = if
side(triv.0, triv.1, position) >= 0,
side(triv.1, triv.2, position) >= 0,
side(triv.2, triv.0, position) >= 0
*/
lines[i + 0] = Line(from: triv.0, to: triv.1, colour: colour)
lines[i + 1] = Line(from: triv.1, to: triv.2, colour: colour)
lines[i + 2] = Line(from: triv.2, to: triv.0, colour: colour)
i += 3
case .aabbFloor(let floorn, let floorp, let floorw, let floord):
o = floorp
n = floorn
let n2 = Vec2f(n.x, n.z) / n.y
let z0 = Vec2f(-floorw, floord).dot(n2)
let z1 = Vec2f(-floorw, -floord).dot(n2)
let z2 = Vec2f( floorw, -floord).dot(n2)
let z3 = Vec2f( floorw, floord).dot(n2)
let c0 = floorp + Vec3f( floorw, z0, -floord)
let c1 = floorp + Vec3f( floorw, z1, floord)
let c2 = floorp + Vec3f(-floorw, z2, floord)
let c3 = floorp + Vec3f(-floorw, z3, -floord)
lines[i + 0] = Line(from: c0, to: c1, colour: XnaColour.GreenYellow)
lines[i + 1] = Line(from: c1, to: c2, colour: XnaColour.GreenYellow)
lines[i + 2] = Line(from: c2, to: c3, colour: XnaColour.GreenYellow)
lines[i + 3] = Line(from: c3, to: c0, colour: XnaColour.GreenYellow)
i += 4
case .quad(let verts, let winding):
/*
let p = (
verts.0 + (verts.1 - verts.0) * position.x,
verts.3 + (verts.2 - verts.3) * position.x,
verts.0 + (verts.3 - verts.0) * position.z,
verts.1 + (verts.2 - verts.1) * position.z)
let xdiff = Vec2f(p.0.x - p.1.x, p.2.x - p.3.x)
let ydiff = Vec2f(p.0.z - p.1.z, p.2.z - p.3.z)
let div = xdiff.cross(ydiff)
guard div != 0.0 else { break }
let d = Vec2f(
Vec2f(p.0.x, p.0.z).cross(Vec2f(p.1.x, p.1.z)),
Vec2f(p.2.x, p.2.z).cross(Vec2f(p.3.x, p.3.z)))
let pos = Vec2f(d.cross(xdiff), d.cross(ydiff)) / div
o = Vec3f(pos.x, 0.0, pos.y)
n = .up
*/
//n = winding == .ccw ? .up : .down
o = verts.0.lerp(verts.3, 0.5).lerp(
verts.2.lerp(verts.1, 0.5), 0.5)
/*
let xy = Vec2d(Double(position.x), Double(position.z))
let a = xy.x - p.0.x
let b = p.1.x - p.0.x
let c = p.3.x - p.0.x
let d = p.0.x - p.1.x + p.2.x - p.3.x
let f = xy.y - p.0.y
let g = p.1.y - p.0.y
let h = p.3.y - p.0.y
let j = p.0.y - p.1.y + p.2.y - p.3.y
let v2 = -c * j - (-d * h)
let v1 = a * j - c * g - (d * f - b * h)
let v0 = a * g - b * f
let vq = (-v1 + sqrt(v1 * v1 - 4.0 * v2 * v0)) / (2.0 * v2)
let uq = (a - c * vq) / (b + d * vq)
let uv = Vec2d(uq, vq)
*/
var colour = XnaColour.GreenYellow
/*
p1 p2
*------*
| |
| |
*------*
p0 p3
*/
let p = winding == .ccw ? (verts.3, verts.2, verts.1, verts.0) : (verts.0, verts.1, verts.2, verts.3)
let vn = (
(verts.1 - verts.0).cross(verts.3 - verts.0),
(verts.2 - verts.1).cross(verts.0 - verts.1),
(verts.3 - verts.2).cross(verts.1 - verts.2),
(verts.0 - verts.3).cross(verts.2 - verts.3)
)
n = (vn.0 + vn.1 + vn.2 + vn.3).normalised
let uv = Self.quadSpaceFromCartesian(quad: p, position: position)
if uv.x >= 0.0 && uv.x <= 1.0 && uv.y >= 0.0 && uv.y <= 1.0
//if uv.x >= -1.0 && uv.x <= 1.0 && uv.y >= -1.0 && uv.y <= 1.0
{
var pp = p.0
pp += (p.1 - p.0) * uv.x
pp += (p.3 - p.0) * uv.y
pp += (p.0 - p.1 + p.2 - p.3) * uv.x * uv.y
lines[i] = Line(from: o, to: pp, colour: XnaColour.Aquamarine)
i += 1
colour = XnaColour.BurlyWood
o = pp
n = vn.0.lerp(vn.3, uv.x).lerp(
vn.2.lerp(vn.1, uv.x), winding == .cw ? uv.y : 1.0 - uv.y).normalised
}
lines[i + 0] = Line(from: verts.0, to: verts.1, colour: colour)
lines[i + 1] = Line(from: verts.1, to: verts.2, colour: colour)
lines[i + 2] = Line(from: verts.2, to: verts.3, colour: colour)
lines[i + 3] = Line(from: verts.3, to: verts.0, colour: colour)
i += 4
}
let p = position
let a = p.dot(n) - o.dot(n)
let pissy = p - n * a
//let d = colin.position - edge.p
//let pissy = edge.p + d.cross(edge.n)
/*
if a > -1.0 && a <= 0.0
{
lines[i + 0] = Line(from: o, to: o + n * max(0, a), colour: XnaColour.Magenta)
lines[i + 1] = Line(from: pissy + .up, to: o, colour: XnaColour.Red)
i += 2
}
*/
lines[i + 0] = Line(from: o, to: o + n * 0.2, colour: XnaColour.Magenta)
i += 1
//lines[i + 4] = Line(from: pissy + Vec3f(-1, 0, 0) * 0.1, to: pissy + Vec3f(1, 0, 0) * 0.1, colour: XnaColour.Red)
//lines[i + 5] = Line(from: pissy + Vec3f( 0,-1, 0) * 0.1, to: pissy + Vec3f(0, 1, 0) * 0.1, colour: XnaColour.Red)
//lines[i + 6] = Line(from: pissy + Vec3f( 0, 0,-1) * 0.1, to: pissy + Vec3f(0, 0, 1) * 0.1, colour: XnaColour.Red)
}
render.drawGizmos(lines: lines)
}
}

View File

@ -0,0 +1,9 @@
import JolkEngine
protocol Actor
{
func update(deltaTime: Float, world: Collision)
var position: Vec3f { get }
var transform: Mat4f { get }
}

View File

@ -0,0 +1,276 @@
import SDL2
import simd
import OpenGL.GL
import JolkEngine
class Colin: Actor
{
var position: Vec3f { _pos }
var transform: Mat4f
{
//.rotate(yaw: angle.x, pitch: angle.y, roll: sin(time)) *
//.scale(Vec3f(1.0 + 0.25 * cos(time), 1.0 + 0.25 * sin(time), 1.0)) *
.rotate(yawPitch: angle) * .translate(-position - Vec3f(0, 1, 0))
}
var angle: Vec2f { return Vec2f(ofsAngle.x + _angle, ofsAngle.y) }
var fov: Float { return _fov }
private var time: Float = 0.0
//private var jumpVel: Float = 0.0
private var velocity: Vec3f = .zero
private var _pos: Vec3f = .zero
private var _pos2D: Vec2f { get { Vec2f(_pos.x, _pos.z) } set(new) { _pos.x = new.x; _pos.z = new.y } }
private var _angle: Float = 0.0
private var ofsAngle: Vec2f = .zero
private var _fov: Float = 60.0
private var nutted = false
private var colinMode = false
private var backPressed = false
private let colinWidth: Float = 0.2
private func move2D(new: Vec2f, edges: [Collision.Edge])
{
let velocity = new - _pos2D
var lastPos = _pos2D
_pos2D = new
var collided = -1
jolk: while (collided != 0)
{
let scabidabadoo = 10
for edge in edges
{
let diff = _pos2D - lastPos
if simd_dot(edge.n, diff) > 0 && simd_dot(edge.n, velocity) > 0 { continue }
let deltaPos = _pos2D - edge.p
let something = deltaPos.cross(edge.n)
if abs(something) * 2.0 < edge.w
{
let dot = simd_dot(edge.n, deltaPos)
if dot > 0 && dot < colinWidth
{
lastPos = _pos2D
_pos2D += edge.n * (colinWidth - dot)
if collided < 0 { collided = scabidabadoo };
}
}
else
{
let vec = (deltaPos - Vec2f(edge.n.y, -edge.n.x) * Float(signOf: something, magnitudeOf: edge.w * 0.5))
let distance2 = vec.len2
let epsilon: Float = 0.001
if distance2 < (colinWidth - epsilon) * (colinWidth - epsilon)
{
let distance = distance2.squareRoot()
lastPos = _pos2D
_pos2D += (vec / distance) * (colinWidth - epsilon - distance)
if collided < 0 { collided = scabidabadoo };
}
}
}
collided -= 1;
if collided <= 0 { break jolk }
}
}
private func response3D(plainPos o: Vec3f, plainNorm n: Vec3f)
{
let height: Float = 1.12
//let diff = _pos - lastPos
//let p = n.y > 1.0 ? _pos : (_pos + 2.0)
let ofs: Vec3f = n.y.sign == .plus ? .zero : .up * height
//let p = if n.y.sign == .plus { _pos } else { _pos + 1.0 }
let p = _pos + ofs
let a = p.dot(n) - o.dot(n)
if a > height * -0.5 && a < 0.0
{
_pos -= n * a
velocity -= velocity.project(n)
}
}
private func move3D(_ deltaTime: Float, world: Collision)
{
var lastPos = _pos
_pos += velocity * deltaTime
for edge in world.edge3d
{
switch edge
{
case .aabbFloor(let normal, let origin, let width, let depth):
//let left = min(edge.v0.x, edge.v1.x, edge.v2.x)
//let right = max(edge.v0.x, edge.v1.x, edge.v2.x)
//let back = min(edge.v0.z, edge.v1.z, edge.v2.z)
//let forward = max(edge.v0.z, edge.v1.z, edge.v2.z)
let left = origin.x - width, right = origin.x + width
let back = origin.z - depth, forward = origin.z + depth
if _pos.x < left || _pos.x > right { continue }
if _pos.z < back || _pos.z > forward { continue }
response3D(plainPos: origin, plainNorm: normal)
case .triangle(let normal, let origin, let verts):
let p = (verts.1 - verts.0, verts.2 - verts.0, _pos - verts.0)
let invDenom = 1.0 / (p.0.x * p.1.z - p.1.x * p.0.z);
let v = invDenom * (p.2.x * p.1.z - p.1.x * p.2.z);
let w = invDenom * (p.0.x * p.2.z - p.2.x * p.0.z);
let x = 1.0 - v - w
//guard v >= 0.0 && w >= 0.0 && v + w <= 1.0 else { break }
guard v >= 0.0 && w >= 0.0 && 0 <= x && x <= 1.0 else { break }
response3D(plainPos: origin, plainNorm: normal)
case .quad(let verts, let winding):
let vn = (
(verts.1 - verts.0).cross(verts.3 - verts.0),
(verts.2 - verts.1).cross(verts.0 - verts.1),
(verts.3 - verts.2).cross(verts.1 - verts.2),
(verts.0 - verts.3).cross(verts.2 - verts.3)
)
let uv = Collision.quadSpaceFromCartesian(quad:
winding == .ccw ? (verts.3, verts.2, verts.1, verts.0) : (verts.0, verts.1, verts.2, verts.3)
, position: _pos)
if uv.x >= 0.0 && uv.x <= 1.0 && uv.y >= 0.0 && uv.y <= 1.0
{
//var pp = p.0
//pp += (p.1 - p.0) * uv.x
//pp += (p.3 - p.0) * uv.y
//pp += (p.0 - p.1 + p.2 - p.3) * uv.x * uv.y
let p = verts.0.lerp(verts.1, uv.x).lerp(
verts.3.lerp(verts.2, uv.x), winding == .cw ? uv.y : 1.0 - uv.y)
let n = vn.0.lerp(vn.3, uv.x).lerp(
vn.2.lerp(vn.1, uv.x), winding == .cw ? uv.y : 1.0 - uv.y).normalised
response3D(plainPos: Vec3f(_pos.x, p.y, _pos.z), plainNorm: n)
}
/*
let p = (
verts.0 + (verts.1 - verts.0) * _pos.x,
verts.3 + (verts.2 - verts.3) * _pos.x,
verts.0 + (verts.3 - verts.0) * _pos.z,
verts.1 + (verts.2 - verts.1) * _pos.z)
let xdiff = Vec2f(p.0.x - p.1.x, p.2.x - p.3.x)
let ydiff = Vec2f(p.0.z - p.1.z, p.2.z - p.3.z)
let div = xdiff.cross(ydiff)
guard div != 0.0 else { break }
let d = Vec2f(
Vec2f(p.0.x, p.0.z).cross(Vec2f(p.1.x, p.1.z)),
Vec2f(p.2.x, p.2.z).cross(Vec2f(p.3.x, p.3.z)))
let pos = Vec2f(d.cross(xdiff), d.cross(ydiff)) / div
*/
default: break
}
}
}
func update(deltaTime: Float, world: Collision)
{
time += deltaTime
let axisRescale = 1.0 / Float(INT16_MAX)
let getAxis = { (axis: SDL_GameControllerAxis) in
Float(SDL_GameControllerGetAxis(sdlPad, axis)) * axisRescale }
let getButton = { (btn: SDL_GameControllerButton) in
SDL_GameControllerGetButton(sdlPad, btn) == SDL_PRESSED }
let stick = Vec2f(
getAxis(SDL_CONTROLLER_AXIS_LEFTX),
getAxis(SDL_CONTROLLER_AXIS_LEFTY))
.cardinalDeadzone(min: 0.1, max: 1)
let turnSpeed = Float.pi * 2 * 0.1
if Input.instance.keyDown(.left)
{
_angle -= turnSpeed * deltaTime
}
if Input.instance.keyDown(.right)
{
_angle += turnSpeed * deltaTime
}
if stick != .zero
{
_angle += stick.x * 2.0 * turnSpeed * deltaTime
}
var moveVec: Vec2f = .zero
let speed: Float = 2
let forward = Vec2f(sin(self._angle), -cos(self._angle))
if Input.instance.keyDown(.up)
{
moveVec += forward * speed
}
if Input.instance.keyDown(.down)
{
moveVec -= forward * speed
}
if stick != .zero
{
moveVec -= forward * stick.y * speed
}
if Input.instance.keyDown(.c)
{
colinMode = !colinMode
}
let lookCone = Float.pi / 2.0
let dst = Vec2f(
getAxis(SDL_CONTROLLER_AXIS_RIGHTX),
getAxis(SDL_CONTROLLER_AXIS_RIGHTY))
.radialDeadzone(min: 0.1, max: 1) * lookCone
ofsAngle = ofsAngle.lerp(dst, 16 * deltaTime)
let targetFov = Float.lerp(60, 20, getAxis(SDL_CONTROLLER_AXIS_TRIGGERRIGHT))
_fov = Float.lerp(_fov, targetFov, 20 * deltaTime)
let right = Vec2f(cos(self._angle), sin(self._angle))
if getButton(SDL_CONTROLLER_BUTTON_LEFTSHOULDER)
{
moveVec -= right * speed
}
if getButton(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER)
{
moveVec += right * speed
}
let back = getButton(SDL_CONTROLLER_BUTTON_BACK)
if !backPressed && back { colinMode = !colinMode }
backPressed = back
if getButton(SDL_CONTROLLER_BUTTON_A) || Input.instance.keyDown(.n)
{
// play nut.wav
nutted = true
}
else { nutted = false }
//move2D(new: _pos2D + velocity, edges: edges)
if Input.instance.keyDown(.z) || getButton(SDL_CONTROLLER_BUTTON_B) { velocity.y = 2.0 }
//_pos.y += jumpVel * deltaTime
//if _pos.y > 0.0 { jumpVel -= 5.4 * deltaTime }
velocity.y -= 5.4 * deltaTime
velocity.x = moveVec.x
velocity.z = moveVec.y
move3D(deltaTime, world: world)
//else if _pos.y < 0.0
//{
// jumpVel = max(jumpVel, 0.0)
// _pos.y = 0.0
//}
}
}

View File

@ -0,0 +1,33 @@
import Foundation
import simd
import JolkEngine
class JolkCube: Actor
{
private var _pos: Vec3f
private var theta: Float = 0.0
init(position: Vec3f)
{
_pos = position
}
var position: Vec3f { _pos }
func update(deltaTime: Float, world: Collision)
{
theta += 15 * deltaTime
_pos.y = 1 + sin(theta * 0.25) * 0.25
}
var transform: Mat4f
{
.translate(_pos) *
.rotate(x: theta * 0.25) *
.rotate(y: Float.rad(fromDeg: theta)) *
// .rotate(axis: .X, angle: theta * 0.25) *
// .rotate(axis: .Y, angle: Float.rad(fromDeg: theta)) *
.scale(scalar: 0.25)
}
}

View File

@ -0,0 +1,33 @@
# Blender 3.6.2 MTL File: 'World.blend'
# www.blender.org
newmtl Cobblestone
Ns 437.449280
Ka 1.000000 1.000000 1.000000
Ke 0.000000 0.000000 0.000000
Ni 1.450000
d 1.000000
illum 1
map_Kd cobblestone.png
map_Ks cobblestone_specular.png
map_Bump -bm 1.000000 cobblestone_normal.png
newmtl Material
Ns 167.965591
Ka 1.000000 1.000000 1.000000
Kd 0.089179 0.800000 0.000000
Ks 0.483607 0.483607 0.483607
Ke 0.000000 0.000000 0.000000
Ni 1.450000
d 1.000000
illum 2
newmtl MetalWall
Ns 55.588612
Ka 0.390244 0.390244 0.390244
Ks 0.573171 0.573171 0.573171
Ke 0.000000 0.000000 0.000000
Ni 9.650000
d 1.000000
illum 3
map_Kd metalwall1.jpg

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 656 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 881 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 KiB

BIN
Sources/Test/World.blend Normal file

Binary file not shown.

View File

@ -0,0 +1,148 @@
import JolkEngine
public struct XnaColour
{
public static let Transparent = Colour.zero
public static let AliceBlue = Colour(fromRgb24: 0xF0F8FF)
public static let AntiqueWhite = Colour(fromRgb24: 0xFAEBD7)
public static let Aqua = Colour(fromRgb24: 0x00FFFF)
public static let Aquamarine = Colour(fromRgb24: 0x7FFFD4)
public static let Azure = Colour(fromRgb24: 0xF0FFFF)
public static let Beige = Colour(fromRgb24: 0xF5F5DC)
public static let Bisque = Colour(fromRgb24: 0xFFE4C4)
public static let Black = Colour.black
public static let BlanchedAlmond = Colour(fromRgb24: 0xFFEBCD)
public static let Blue = Colour(fromRgb24: 0x0000FF)
public static let BlueViolet = Colour(fromRgb24: 0x8A2BE2)
public static let Brown = Colour(fromRgb24: 0xA52A2A)
public static let BurlyWood = Colour(fromRgb24: 0xDEB887)
public static let CadetBlue = Colour(fromRgb24: 0x5F9EA0)
public static let Chartreuse = Colour(fromRgb24: 0x7FFF00)
public static let Chocolate = Colour(fromRgb24: 0xD2691E)
public static let Coral = Colour(fromRgb24: 0xFF7F50)
public static let CornflowerBlue = Colour(fromRgb24: 0x6495ED)
public static let Cornsilk = Colour(fromRgb24: 0xFFF8DC)
public static let Crimson = Colour(fromRgb24: 0xDC143C)
public static let Cyan = Colour(fromRgb24: 0x00FFFF)
public static let DarkBlue = Colour(fromRgb24: 0x00008B)
public static let DarkCyan = Colour(fromRgb24: 0x008B8B)
public static let DarkGoldenrod = Colour(fromRgb24: 0xB8860B)
public static let DarkGray = Colour(fromRgb24: 0xA9A9A9)
public static let DarkGreen = Colour(fromRgb24: 0x006400)
public static let DarkKhaki = Colour(fromRgb24: 0xBDB76B)
public static let DarkMagenta = Colour(fromRgb24: 0x8B008B)
public static let DarkOliveGreen = Colour(fromRgb24: 0x556B2F)
public static let DarkOrange = Colour(fromRgb24: 0xFF8C00)
public static let DarkOrchid = Colour(fromRgb24: 0x9932CC)
public static let DarkRed = Colour(fromRgb24: 0x8B0000)
public static let DarkSalmon = Colour(fromRgb24: 0xE9967A)
public static let DarkSeaGreen = Colour(fromRgb24: 0x8FBC8B)
public static let DarkSlateBlue = Colour(fromRgb24: 0x483D8B)
public static let DarkSlateGray = Colour(fromRgb24: 0x2F4F4F)
public static let DarkTurquoise = Colour(fromRgb24: 0x00CED1)
public static let DarkViolet = Colour(fromRgb24: 0x9400D3)
public static let DeepPink = Colour(fromRgb24: 0xFF1493)
public static let DeepSkyBlue = Colour(fromRgb24: 0x00BFFF)
public static let DimGray = Colour(fromRgb24: 0x696969)
public static let DodgerBlue = Colour(fromRgb24: 0x1E90FF)
public static let Firebrick = Colour(fromRgb24: 0xB22222)
public static let FloralWhite = Colour(fromRgb24: 0xFFFAF0)
public static let ForestGreen = Colour(fromRgb24: 0x228B22)
public static let Fuchsia = Colour(fromRgb24: 0xFF00FF)
public static let Gainsboro = Colour(fromRgb24: 0xDCDCDC)
public static let GhostWhite = Colour(fromRgb24: 0xF8F8FF)
public static let Gold = Colour(fromRgb24: 0xFFD700)
public static let Goldenrod = Colour(fromRgb24: 0xDAA520)
public static let Gray = Colour(fromRgb24: 0x808080)
public static let Green = Colour(fromRgb24: 0x008000)
public static let GreenYellow = Colour(fromRgb24: 0xADFF2F)
public static let Honeydew = Colour(fromRgb24: 0xF0FFF0)
public static let HotPink = Colour(fromRgb24: 0xFF69B4)
public static let IndianRed = Colour(fromRgb24: 0xCD5C5C)
public static let Indigo = Colour(fromRgb24: 0x4B0082)
public static let Ivory = Colour(fromRgb24: 0xFFFFF0)
public static let Khaki = Colour(fromRgb24: 0xF0E68C)
public static let Lavender = Colour(fromRgb24: 0xE6E6FA)
public static let LavenderBlush = Colour(fromRgb24: 0xFFF0F5)
public static let LawnGreen = Colour(fromRgb24: 0x7CFC00)
public static let LemonChiffon = Colour(fromRgb24: 0xFFFACD)
public static let LightBlue = Colour(fromRgb24: 0xADD8E6)
public static let LightCoral = Colour(fromRgb24: 0xF08080)
public static let LightCyan = Colour(fromRgb24: 0xE0FFFF)
public static let LightGoldenrodYellow = Colour(fromRgb24: 0xFAFAD2)
public static let LightGray = Colour(fromRgb24: 0xD3D3D3)
public static let LightGreen = Colour(fromRgb24: 0x90EE90)
public static let LightPink = Colour(fromRgb24: 0xFFB6C1)
public static let LightSalmon = Colour(fromRgb24: 0xFFA07A)
public static let LightSeaGreen = Colour(fromRgb24: 0x20B2AA)
public static let LightSkyBlue = Colour(fromRgb24: 0x87CEFA)
public static let LightSlateGray = Colour(fromRgb24: 0x778899)
public static let LightSteelBlue = Colour(fromRgb24: 0xB0C4DE)
public static let LightYellow = Colour(fromRgb24: 0xFFFFE0)
public static let Lime = Colour(fromRgb24: 0x00FF00)
public static let LimeGreen = Colour(fromRgb24: 0x32CD32)
public static let Linen = Colour(fromRgb24: 0xFAF0E6)
public static let Magenta = Colour(fromRgb24: 0xFF00FF)
public static let Maroon = Colour(fromRgb24: 0x800000)
public static let MediumAquamarine = Colour(fromRgb24: 0x66CDAA)
public static let MediumBlue = Colour(fromRgb24: 0x0000CD)
public static let MediumOrchid = Colour(fromRgb24: 0xBA55D3)
public static let MediumPurple = Colour(fromRgb24: 0x9370DB)
public static let MediumSeaGreen = Colour(fromRgb24: 0x3CB371)
public static let MediumSlateBlue = Colour(fromRgb24: 0x7B68EE)
public static let MediumSpringGreen = Colour(fromRgb24: 0x00FA9A)
public static let MediumTurquoise = Colour(fromRgb24: 0x48D1CC)
public static let MediumVioletRed = Colour(fromRgb24: 0xC71585)
public static let MidnightBlue = Colour(fromRgb24: 0x191970)
public static let MintCream = Colour(fromRgb24: 0xF5FFFA)
public static let MistyRose = Colour(fromRgb24: 0xFFE4E1)
public static let Moccasin = Colour(fromRgb24: 0xFFE4B5)
public static let MonoGameOrange = Colour(fromRgb24: 0xE73C00)
public static let NavajoWhite = Colour(fromRgb24: 0xFFDEAD)
public static let Navy = Colour(fromRgb24: 0x000080)
public static let OldLace = Colour(fromRgb24: 0xFDF5E6)
public static let Olive = Colour(fromRgb24: 0x808000)
public static let OliveDrab = Colour(fromRgb24: 0x6B8E23)
public static let Orange = Colour(fromRgb24: 0xFFA500)
public static let OrangeRed = Colour(fromRgb24: 0xFF4500)
public static let Orchid = Colour(fromRgb24: 0xDA70D6)
public static let PaleGoldenrod = Colour(fromRgb24: 0xEEE8AA)
public static let PaleGreen = Colour(fromRgb24: 0x98FB98)
public static let PaleTurquoise = Colour(fromRgb24: 0xAFEEEE)
public static let PaleVioletRed = Colour(fromRgb24: 0xDB7093)
public static let PapayaWhip = Colour(fromRgb24: 0xFFEFD5)
public static let PeachPuff = Colour(fromRgb24: 0xFFDAB9)
public static let Peru = Colour(fromRgb24: 0xCD853F)
public static let Pink = Colour(fromRgb24: 0xFFC0CB)
public static let Plum = Colour(fromRgb24: 0xDDA0DD)
public static let PowderBlue = Colour(fromRgb24: 0xB0E0E6)
public static let Purple = Colour(fromRgb24: 0x800080)
public static let Red = Colour(fromRgb24: 0xFF0000)
public static let RosyBrown = Colour(fromRgb24: 0xBC8F8F)
public static let RoyalBlue = Colour(fromRgb24: 0x4169E1)
public static let SaddleBrown = Colour(fromRgb24: 0x8B4513)
public static let Salmon = Colour(fromRgb24: 0xFA8072)
public static let SandyBrown = Colour(fromRgb24: 0xF4A460)
public static let SeaGreen = Colour(fromRgb24: 0x2E8B57)
public static let SeaShell = Colour(fromRgb24: 0xFFF5EE)
public static let Sienna = Colour(fromRgb24: 0xA0522D)
public static let Silver = Colour(fromRgb24: 0xC0C0C0)
public static let SkyBlue = Colour(fromRgb24: 0x87CEEB)
public static let SlateBlue = Colour(fromRgb24: 0x6A5ACD)
public static let SlateGray = Colour(fromRgb24: 0x708090)
public static let Snow = Colour(fromRgb24: 0xFFFAFA)
public static let SpringGreen = Colour(fromRgb24: 0x00FF7F)
public static let SteelBlue = Colour(fromRgb24: 0x4682B4)
public static let Tan = Colour(fromRgb24: 0xD2B48C)
public static let Teal = Colour(fromRgb24: 0x008080)
public static let Thistle = Colour(fromRgb24: 0xD8BFD8)
public static let Tomato = Colour(fromRgb24: 0xFF6347)
public static let Turquoise = Colour(fromRgb24: 0x40E0D0)
public static let Violet = Colour(fromRgb24: 0xEE82EE)
public static let Wheat = Colour(fromRgb24: 0xF5DEB3)
public static let White = Colour.white
public static let WhiteSmoke = Colour(fromRgb24: 0xF5F5F5)
public static let Yellow = Colour(fromRgb24: 0xFFFF00)
public static let YellowGreen = Colour(fromRgb24: 0x9ACD32)
}