Compare commits

...
25 Commits
Author SHA1 Message Date
jonas 762ed3c478 closure bump 2026-09-12 14:44:08 +02:00
jonas 5c5c81325d fix(build): app name 2026-04-02 19:19:26 +02:00
jonas d1211b7157 fix(texture): display inf rec 2026-04-02 19:17:23 +02:00
jonas 99bf883eeb fix(flake): default app name 2026-04-02 18:51:15 +02:00
jonas 078ad2c401 build: install scenes properly 2026-04-02 18:17:22 +02:00
jonas 3a49460fe6 feat(sphere): add texture sphere 2026-04-02 15:10:09 +02:00
jonas 684cc19302 fix(expression): PartialEq and PartialOrd impls 2026-04-02 14:48:14 +02:00
jonas 0e919c339c feat(texture): add plane textures 2026-04-02 14:41:01 +02:00
jonas 5605ad0901 fix: environment warnings 2026-04-01 01:45:35 +02:00
jonas 3e5f23a3bf feat(rt-lisp): add more math overloads 2026-04-01 01:43:50 +02:00
jonas 5aeaf72af1 feat(macro): show invalid args 2026-04-01 01:24:47 +02:00
jonas 6e6a3e8a27 feat: update demo 2026-04-01 00:56:36 +02:00
jonas aa0ba6ed7a feat(scenes): add materials.lisp 2026-04-01 00:37:25 +02:00
jonas 5ffc390d2c feat(rt_interp): enable file locations for include 2026-04-01 00:36:03 +02:00
jonas 48d4039c31 feat(core): add load and include prelude 2026-04-01 00:35:52 +02:00
jonas 3cb3e4a8fa feat(sphere): inner intersection 2026-03-31 23:01:55 +02:00
jonas fc40e0b798 feat(camera): render anim using ffmpeg 2026-03-31 20:15:30 +02:00
jonas d0840759b3 feat(core): Display for EvalError + ParserError 2026-03-31 20:14:49 +02:00
jonas d4281d3538 feat(tokenizer): allow negative numbers 2026-03-31 04:32:08 +02:00
jonas 3c73077837 feat(anim): add frame-based animations 2026-03-31 04:12:39 +02:00
jonas 36b9ad4c0d feat(lispers-macro): show function name in application error 2026-03-31 04:10:18 +02:00
jonas 7d70066213 feat(expr): from fixed sized array to Expr 2026-03-31 04:09:52 +02:00
jonas 72c0cc8445 fix: devShell deps 2026-03-31 04:09:36 +02:00
jonas 0bdeb2ceab build: use flake-parts 2026-03-31 01:06:03 +02:00
jonas 0ca3ec6973 fix: ffmpeg-next build 2026-03-31 00:48:57 +02:00
29 changed files with 2498 additions and 1145 deletions
Generated
+967 -315
View File
File diff suppressed because it is too large Load Diff
+9 -7
View File
@@ -4,7 +4,7 @@ description = "lisp interpreter in rust for raytracing"
publish = false publish = false
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2024"
[lib] [lib]
name = "lispers" name = "lispers"
@@ -32,14 +32,16 @@ members = [ "lispers-core", "lispers-macro"]
[workspace.dependencies] [workspace.dependencies]
lispers-core = {path = "lispers-core"} lispers-core = {path = "lispers-core"}
lispers-macro = {path = "lispers-macro"} lispers-macro = {path = "lispers-macro"}
as-any = "0.3.1" as-any = "0.3.2"
[dependencies] [dependencies]
as-any = {workspace = true} as-any = {workspace = true}
futures = "0.3.30" futures = "0.3.32"
image = "0.25.5" image = "0.25.10"
nalgebra = "0.33.2" nalgebra = "0.34.2"
nix = "0.29.0" nix = "0.31.2"
rayon = "1.10.0" rayon = "1.11.0"
lispers-core = {workspace = true} lispers-core = {workspace = true}
lispers-macro = {workspace = true} lispers-macro = {workspace = true}
video-rs = { version = "0.11.0", features = ["ndarray"] }
ndarray = "0.17.2"
+39
View File
@@ -0,0 +1,39 @@
use std::path::Path;
fn copy_dir(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> std::io::Result<()> {
std::fs::create_dir_all(&dst)?;
for entry in std::fs::read_dir(src)? {
let entry = entry?;
if entry.file_type()?.is_dir() {
copy_dir(entry.path(), dst.as_ref().join(entry.file_name()))?;
} else {
std::fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
}
}
Ok(())
}
fn main() {
let use_local_scenes = std::env::var("LISPERS_USE_LOCAL_SCENES").unwrap_or_default() == "1";
let no_copy = std::env::var("LISPERS_DONT_COPY_SCENES").unwrap_or_default() == "1";
let out_dir = match std::env::var("LISPERS_OUT_DIR") {
Ok(val) => val,
Err(_) => std::env::var("OUT_DIR").unwrap(),
};
let mut scenes_dir = Path::new(&std::env::var("CARGO_MANIFEST_DIR").unwrap())
.canonicalize()
.unwrap()
.join("scenes");
if !use_local_scenes {
let tgt_scenes_dir = Path::new(&out_dir).join("scenes");
if !no_copy {
copy_dir(&scenes_dir, &tgt_scenes_dir).expect("Failed to copy scenes directory");
}
scenes_dir = tgt_scenes_dir;
}
println!("cargo:rustc-env=SCENES_DIR={}", scenes_dir.display());
}
Generated
+86 -671
View File
@@ -8,82 +8,15 @@
"flake-compat": [ "flake-compat": [
"crate2nix" "crate2nix"
], ],
"nixpkgs": "nixpkgs", "git-hooks": "git-hooks",
"pre-commit-hooks": [ "nixpkgs": "nixpkgs"
"crate2nix"
]
}, },
"locked": { "locked": {
"lastModified": 1709700175, "lastModified": 1767714506,
"narHash": "sha256-A0/6ZjLmT9qdYzKHmevnEIC7G+GiZ4UCr8v0poRPzds=", "narHash": "sha256-WaTs0t1CxhgxbIuvQ97OFhDTVUGd1HA+KzLZUZBhe0s=",
"owner": "cachix", "owner": "cachix",
"repo": "cachix", "repo": "cachix",
"rev": "be97b37989f11b724197b5f4c7ffd78f12c8c4bf", "rev": "894c649f0daaa38bbcfb21de64be47dfa7cd0ec9",
"type": "github"
},
"original": {
"owner": "cachix",
"ref": "latest",
"repo": "cachix",
"type": "github"
}
},
"cachix_2": {
"inputs": {
"devenv": [
"crate2nix",
"crate2nix_stable"
],
"flake-compat": [
"crate2nix",
"crate2nix_stable"
],
"nixpkgs": "nixpkgs_2",
"pre-commit-hooks": [
"crate2nix",
"crate2nix_stable"
]
},
"locked": {
"lastModified": 1716549461,
"narHash": "sha256-lHy5kgx6J8uD+16SO47dPrbob98sh+W1tf4ceSqPVK4=",
"owner": "cachix",
"repo": "cachix",
"rev": "e2bb269fb8c0828d5d4d2d7b8d09ea85abcacbd4",
"type": "github"
},
"original": {
"owner": "cachix",
"ref": "latest",
"repo": "cachix",
"type": "github"
}
},
"cachix_3": {
"inputs": {
"devenv": [
"crate2nix",
"crate2nix_stable",
"crate2nix_stable"
],
"flake-compat": [
"crate2nix",
"crate2nix_stable",
"crate2nix_stable"
],
"nixpkgs": "nixpkgs_3",
"pre-commit-hooks": [
"crate2nix",
"crate2nix_stable",
"crate2nix_stable"
]
},
"locked": {
"lastModified": 1716549461,
"narHash": "sha256-lHy5kgx6J8uD+16SO47dPrbob98sh+W1tf4ceSqPVK4=",
"owner": "cachix",
"repo": "cachix",
"rev": "e2bb269fb8c0828d5d4d2d7b8d09ea85abcacbd4",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -96,162 +29,42 @@
"crate2nix": { "crate2nix": {
"inputs": { "inputs": {
"cachix": "cachix", "cachix": "cachix",
"crate2nix_stable": "crate2nix_stable",
"devshell": "devshell_3",
"flake-compat": "flake-compat_3",
"flake-parts": "flake-parts_3",
"nix-test-runner": "nix-test-runner_3",
"nixpkgs": [
"nixpkgs"
],
"pre-commit-hooks": "pre-commit-hooks_3"
},
"locked": {
"lastModified": 1732039290,
"narHash": "sha256-LQKY7bShf2H9kJouxa9ZspfdrulnZF9o4kLTqGqCDYM=",
"owner": "nix-community",
"repo": "crate2nix",
"rev": "9ff208ce7f5a482272b1bcefbe363c772d7ff914",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "crate2nix",
"type": "github"
}
},
"crate2nix_stable": {
"inputs": {
"cachix": "cachix_2",
"crate2nix_stable": "crate2nix_stable_2",
"devshell": "devshell_2",
"flake-compat": "flake-compat_2",
"flake-parts": "flake-parts_2",
"nix-test-runner": "nix-test-runner_2",
"nixpkgs": "nixpkgs_5",
"pre-commit-hooks": "pre-commit-hooks_2"
},
"locked": {
"lastModified": 1719760004,
"narHash": "sha256-esWhRnt7FhiYq0CcIxw9pvH+ybOQmWBfHYMtleaMhBE=",
"owner": "nix-community",
"repo": "crate2nix",
"rev": "1dee214bb20855fa3e1e7bb98d28922ddaff8c57",
"type": "github"
},
"original": {
"owner": "nix-community",
"ref": "0.14.1",
"repo": "crate2nix",
"type": "github"
}
},
"crate2nix_stable_2": {
"inputs": {
"cachix": "cachix_3",
"crate2nix_stable": "crate2nix_stable_3",
"devshell": "devshell", "devshell": "devshell",
"flake-compat": "flake-compat", "flake-compat": "flake-compat",
"flake-parts": "flake-parts", "flake-parts": "flake-parts",
"nix-test-runner": "nix-test-runner", "nix-test-runner": "nix-test-runner",
"nixpkgs": "nixpkgs_4", "nixpkgs": [
"nixpkgs"
],
"pre-commit-hooks": "pre-commit-hooks" "pre-commit-hooks": "pre-commit-hooks"
}, },
"locked": { "locked": {
"lastModified": 1712821484, "lastModified": 1774369503,
"narHash": "sha256-rGT3CW64cJS9nlnWPFWSc1iEa3dNZecVVuPVGzcsHe8=", "narHash": "sha256-YeCF4iBhlvTqkn4mihjZgixnDcEVgfyQlNeBsbLYUgQ=",
"owner": "nix-community", "owner": "nix-community",
"repo": "crate2nix", "repo": "crate2nix",
"rev": "42883afcad3823fa5811e967fb7bff54bc3c9d6d", "rev": "b873ca53dd64e12340416f0fd5e3b33792b9c17b",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "nix-community", "owner": "nix-community",
"ref": "0.14.0",
"repo": "crate2nix",
"type": "github"
}
},
"crate2nix_stable_3": {
"inputs": {
"flake-utils": "flake-utils"
},
"locked": {
"lastModified": 1702842982,
"narHash": "sha256-A9AowkHIjsy1a4LuiPiVP88FMxyCWK41flZEZOUuwQM=",
"owner": "nix-community",
"repo": "crate2nix",
"rev": "75ac2973affa6b9b4f661a7b592cba6e4f51d426",
"type": "github"
},
"original": {
"owner": "nix-community",
"ref": "0.12.0",
"repo": "crate2nix", "repo": "crate2nix",
"type": "github" "type": "github"
} }
}, },
"devshell": { "devshell": {
"inputs": { "inputs": {
"flake-utils": "flake-utils_2",
"nixpkgs": [
"crate2nix",
"crate2nix_stable",
"crate2nix_stable",
"nixpkgs"
]
},
"locked": {
"lastModified": 1717408969,
"narHash": "sha256-Q0OEFqe35fZbbRPPRdrjTUUChKVhhWXz3T9ZSKmaoVY=",
"owner": "numtide",
"repo": "devshell",
"rev": "1ebbe68d57457c8cae98145410b164b5477761f4",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "devshell",
"type": "github"
}
},
"devshell_2": {
"inputs": {
"flake-utils": "flake-utils_3",
"nixpkgs": [
"crate2nix",
"crate2nix_stable",
"nixpkgs"
]
},
"locked": {
"lastModified": 1717408969,
"narHash": "sha256-Q0OEFqe35fZbbRPPRdrjTUUChKVhhWXz3T9ZSKmaoVY=",
"owner": "numtide",
"repo": "devshell",
"rev": "1ebbe68d57457c8cae98145410b164b5477761f4",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "devshell",
"type": "github"
}
},
"devshell_3": {
"inputs": {
"flake-utils": "flake-utils_4",
"nixpkgs": [ "nixpkgs": [
"crate2nix", "crate2nix",
"nixpkgs" "nixpkgs"
] ]
}, },
"locked": { "locked": {
"lastModified": 1711099426, "lastModified": 1768818222,
"narHash": "sha256-HzpgM/wc3aqpnHJJ2oDqPBkNsqWbW0WfWUO8lKu8nGk=", "narHash": "sha256-460jc0+CZfyaO8+w8JNtlClB2n4ui1RbHfPTLkpwhU8=",
"owner": "numtide", "owner": "numtide",
"repo": "devshell", "repo": "devshell",
"rev": "2d45b54ca4a183f2fdcf4b19c895b64fbf620ee8", "rev": "255a2b1725a20d060f566e4755dbf571bbbb5f76",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -262,40 +75,12 @@
}, },
"flake-compat": { "flake-compat": {
"locked": { "locked": {
"lastModified": 1696426674, "lastModified": 1733328505,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", "narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", "rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
"revCount": 57, "revCount": 69,
"type": "tarball", "type": "tarball",
"url": "https://api.flakehub.com/f/pinned/edolstra/flake-compat/1.0.1/018afb31-abd1-7bff-a5e4-cff7e18efb7a/source.tar.gz" "url": "https://api.flakehub.com/f/pinned/edolstra/flake-compat/1.1.0/01948eb7-9cba-704f-bbf3-3fa956735b52/source.tar.gz"
},
"original": {
"type": "tarball",
"url": "https://flakehub.com/f/edolstra/flake-compat/1.tar.gz"
}
},
"flake-compat_2": {
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"revCount": 57,
"type": "tarball",
"url": "https://api.flakehub.com/f/pinned/edolstra/flake-compat/1.0.1/018afb31-abd1-7bff-a5e4-cff7e18efb7a/source.tar.gz"
},
"original": {
"type": "tarball",
"url": "https://flakehub.com/f/edolstra/flake-compat/1.tar.gz"
}
},
"flake-compat_3": {
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"revCount": 57,
"type": "tarball",
"url": "https://api.flakehub.com/f/pinned/edolstra/flake-compat/1.0.1/018afb31-abd1-7bff-a5e4-cff7e18efb7a/source.tar.gz"
}, },
"original": { "original": {
"type": "tarball", "type": "tarball",
@@ -306,17 +91,15 @@
"inputs": { "inputs": {
"nixpkgs-lib": [ "nixpkgs-lib": [
"crate2nix", "crate2nix",
"crate2nix_stable",
"crate2nix_stable",
"nixpkgs" "nixpkgs"
] ]
}, },
"locked": { "locked": {
"lastModified": 1719745305, "lastModified": 1768135262,
"narHash": "sha256-xwgjVUpqSviudEkpQnioeez1Uo2wzrsMaJKJClh+Bls=", "narHash": "sha256-PVvu7OqHBGWN16zSi6tEmPwwHQ4rLPU9Plvs8/1TUBY=",
"owner": "hercules-ci", "owner": "hercules-ci",
"repo": "flake-parts", "repo": "flake-parts",
"rev": "c3c5ecc05edc7dafba779c6c1a61cd08ac6583e9", "rev": "80daad04eddbbf5a4d883996a73f3f542fa437ac",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -327,152 +110,47 @@
}, },
"flake-parts_2": { "flake-parts_2": {
"inputs": { "inputs": {
"nixpkgs-lib": [ "nixpkgs-lib": "nixpkgs-lib"
},
"locked": {
"lastModified": 1772408722,
"narHash": "sha256-rHuJtdcOjK7rAHpHphUb1iCvgkU3GpfvicLMwwnfMT0=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "f20dc5d9b8027381c474144ecabc9034d6a839a3",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"git-hooks": {
"inputs": {
"flake-compat": [
"crate2nix", "crate2nix",
"crate2nix_stable", "cachix",
"flake-compat"
],
"gitignore": "gitignore",
"nixpkgs": [
"crate2nix",
"cachix",
"nixpkgs" "nixpkgs"
] ]
}, },
"locked": { "locked": {
"lastModified": 1719745305, "lastModified": 1765404074,
"narHash": "sha256-xwgjVUpqSviudEkpQnioeez1Uo2wzrsMaJKJClh+Bls=", "narHash": "sha256-+ZDU2d+vzWkEJiqprvV5PR26DVFN2vgddwG5SnPZcUM=",
"owner": "hercules-ci", "owner": "cachix",
"repo": "flake-parts", "repo": "git-hooks.nix",
"rev": "c3c5ecc05edc7dafba779c6c1a61cd08ac6583e9", "rev": "2d6f58930fbcd82f6f9fd59fb6d13e37684ca529",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "hercules-ci", "owner": "cachix",
"repo": "flake-parts", "repo": "git-hooks.nix",
"type": "github"
}
},
"flake-parts_3": {
"inputs": {
"nixpkgs-lib": [
"crate2nix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1712014858,
"narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "9126214d0a59633752a136528f5f3b9aa8565b7d",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1694529238,
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_2": {
"inputs": {
"systems": "systems_2"
},
"locked": {
"lastModified": 1701680307,
"narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_3": {
"inputs": {
"systems": "systems_3"
},
"locked": {
"lastModified": 1701680307,
"narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_4": {
"inputs": {
"systems": "systems_4"
},
"locked": {
"lastModified": 1701680307,
"narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_5": {
"inputs": {
"systems": "systems_5"
},
"locked": {
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_6": {
"inputs": {
"systems": "systems_6"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github" "type": "github"
} }
}, },
@@ -480,9 +158,8 @@
"inputs": { "inputs": {
"nixpkgs": [ "nixpkgs": [
"crate2nix", "crate2nix",
"crate2nix_stable", "cachix",
"crate2nix_stable", "git-hooks",
"pre-commit-hooks",
"nixpkgs" "nixpkgs"
] ]
}, },
@@ -501,29 +178,6 @@
} }
}, },
"gitignore_2": { "gitignore_2": {
"inputs": {
"nixpkgs": [
"crate2nix",
"crate2nix_stable",
"pre-commit-hooks",
"nixpkgs"
]
},
"locked": {
"lastModified": 1709087332,
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"gitignore_3": {
"inputs": { "inputs": {
"nixpkgs": [ "nixpkgs": [
"crate2nix", "crate2nix",
@@ -561,45 +215,13 @@
"type": "github" "type": "github"
} }
}, },
"nix-test-runner_2": {
"flake": false,
"locked": {
"lastModified": 1588761593,
"narHash": "sha256-FKJykltAN/g3eIceJl4SfDnnyuH2jHImhMrXS2KvGIs=",
"owner": "stoeffel",
"repo": "nix-test-runner",
"rev": "c45d45b11ecef3eb9d834c3b6304c05c49b06ca2",
"type": "github"
},
"original": {
"owner": "stoeffel",
"repo": "nix-test-runner",
"type": "github"
}
},
"nix-test-runner_3": {
"flake": false,
"locked": {
"lastModified": 1588761593,
"narHash": "sha256-FKJykltAN/g3eIceJl4SfDnnyuH2jHImhMrXS2KvGIs=",
"owner": "stoeffel",
"repo": "nix-test-runner",
"rev": "c45d45b11ecef3eb9d834c3b6304c05c49b06ca2",
"type": "github"
},
"original": {
"owner": "stoeffel",
"repo": "nix-test-runner",
"type": "github"
}
},
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1700612854, "lastModified": 1765186076,
"narHash": "sha256-yrQ8osMD+vDLGFX7pcwsY/Qr5PUd6OmDMYJZzZi0+zc=", "narHash": "sha256-hM20uyap1a0M9d344I692r+ik4gTMyj60cQWO+hAYP8=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "19cbff58383a4ae384dea4d1d0c823d72b49d614", "rev": "addf7cf5f383a3101ecfba091b98d0a1263dc9b8",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -609,13 +231,28 @@
"type": "github" "type": "github"
} }
}, },
"nixpkgs-lib": {
"locked": {
"lastModified": 1772328832,
"narHash": "sha256-e+/T/pmEkLP6BHhYjx6GmwP5ivonQQn0bJdH9YrRB+Q=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"rev": "c185c7a5e5dd8f9add5b2f8ebeff00888b070742",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixpkgs.lib",
"type": "github"
}
},
"nixpkgs_2": { "nixpkgs_2": {
"locked": { "locked": {
"lastModified": 1715534503, "lastModified": 1774386573,
"narHash": "sha256-5ZSVkFadZbFP1THataCaSf0JH2cAH3S29hU9rrxTEqk=", "narHash": "sha256-4hAV26quOxdC6iyG7kYaZcM3VOskcPUrdCQd/nx8obc=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "2057814051972fa1453ddfb0d98badbea9b83c06", "rev": "46db2e09e1d3f113a13c0d7b81e2f221c63b8ce9",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -625,156 +262,24 @@
"type": "github" "type": "github"
} }
}, },
"nixpkgs_3": {
"locked": {
"lastModified": 1715534503,
"narHash": "sha256-5ZSVkFadZbFP1THataCaSf0JH2cAH3S29hU9rrxTEqk=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "2057814051972fa1453ddfb0d98badbea9b83c06",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_4": {
"locked": {
"lastModified": 1719506693,
"narHash": "sha256-C8e9S7RzshSdHB7L+v9I51af1gDM5unhJ2xO1ywxNH8=",
"path": "/nix/store/4p0avw1s3vf27hspgqsrqs37gxk4i83i-source",
"rev": "b2852eb9365c6de48ffb0dc2c9562591f652242a",
"type": "path"
},
"original": {
"id": "nixpkgs",
"type": "indirect"
}
},
"nixpkgs_5": {
"locked": {
"lastModified": 1719506693,
"narHash": "sha256-C8e9S7RzshSdHB7L+v9I51af1gDM5unhJ2xO1ywxNH8=",
"path": "/nix/store/4p0avw1s3vf27hspgqsrqs37gxk4i83i-source",
"rev": "b2852eb9365c6de48ffb0dc2c9562591f652242a",
"type": "path"
},
"original": {
"id": "nixpkgs",
"type": "indirect"
}
},
"nixpkgs_6": {
"locked": {
"lastModified": 1717179513,
"narHash": "sha256-vboIEwIQojofItm2xGCdZCzW96U85l9nDW3ifMuAIdM=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "63dacb46bf939521bdc93981b4cbb7ecb58427a0",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "24.05",
"repo": "nixpkgs",
"type": "github"
}
},
"pre-commit-hooks": { "pre-commit-hooks": {
"inputs": { "inputs": {
"flake-compat": [ "flake-compat": [
"crate2nix", "crate2nix",
"crate2nix_stable",
"crate2nix_stable",
"flake-compat"
],
"gitignore": "gitignore",
"nixpkgs": [
"crate2nix",
"crate2nix_stable",
"crate2nix_stable",
"nixpkgs"
],
"nixpkgs-stable": [
"crate2nix",
"crate2nix_stable",
"crate2nix_stable",
"nixpkgs"
]
},
"locked": {
"lastModified": 1719259945,
"narHash": "sha256-F1h+XIsGKT9TkGO3omxDLEb/9jOOsI6NnzsXFsZhry4=",
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"rev": "0ff4381bbb8f7a52ca4a851660fc7a437a4c6e07",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"type": "github"
}
},
"pre-commit-hooks_2": {
"inputs": {
"flake-compat": [
"crate2nix",
"crate2nix_stable",
"flake-compat" "flake-compat"
], ],
"gitignore": "gitignore_2", "gitignore": "gitignore_2",
"nixpkgs": [ "nixpkgs": [
"crate2nix", "crate2nix",
"crate2nix_stable",
"nixpkgs"
],
"nixpkgs-stable": [
"crate2nix",
"crate2nix_stable",
"nixpkgs" "nixpkgs"
] ]
}, },
"locked": { "locked": {
"lastModified": 1719259945, "lastModified": 1769069492,
"narHash": "sha256-F1h+XIsGKT9TkGO3omxDLEb/9jOOsI6NnzsXFsZhry4=", "narHash": "sha256-Efs3VUPelRduf3PpfPP2ovEB4CXT7vHf8W+xc49RL/U=",
"owner": "cachix", "owner": "cachix",
"repo": "pre-commit-hooks.nix", "repo": "pre-commit-hooks.nix",
"rev": "0ff4381bbb8f7a52ca4a851660fc7a437a4c6e07", "rev": "a1ef738813b15cf8ec759bdff5761b027e3e1d23",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"type": "github"
}
},
"pre-commit-hooks_3": {
"inputs": {
"flake-compat": [
"crate2nix",
"flake-compat"
],
"flake-utils": "flake-utils_5",
"gitignore": "gitignore_3",
"nixpkgs": [
"crate2nix",
"nixpkgs"
],
"nixpkgs-stable": [
"crate2nix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1712055707,
"narHash": "sha256-4XLvuSIDZJGS17xEwSrNuJLL7UjDYKGJSbK1WWX2AK8=",
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
"rev": "e35aed5fda3cc79f88ed7f1795021e559582093a",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -786,8 +291,8 @@
"root": { "root": {
"inputs": { "inputs": {
"crate2nix": "crate2nix", "crate2nix": "crate2nix",
"flake-utils": "flake-utils_6", "flake-parts": "flake-parts_2",
"nixpkgs": "nixpkgs_6", "nixpkgs": "nixpkgs_2",
"rust-overlay": "rust-overlay" "rust-overlay": "rust-overlay"
} }
}, },
@@ -798,11 +303,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1731983527, "lastModified": 1774667365,
"narHash": "sha256-JECaBgC0pQ91Hq3W4unH6K9to8s2Zl2sPNu7bLOv4ek=", "narHash": "sha256-+JamhonkPyti+oqfl1ySAyF2L02adhCEcdZOzpSukq8=",
"owner": "oxalica", "owner": "oxalica",
"repo": "rust-overlay", "repo": "rust-overlay",
"rev": "71287228d96e9568e1e70c6bbfa3f992d145947b", "rev": "98caaa8cd1fbcc45913d1bb2b7fbabcf3e8d967a",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -810,96 +315,6 @@
"repo": "rust-overlay", "repo": "rust-overlay",
"type": "github" "type": "github"
} }
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_3": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_4": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_5": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_6": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
} }
}, },
"root": "root", "root": "root",
+65 -34
View File
@@ -2,39 +2,35 @@
description = "Rust-Nix"; description = "Rust-Nix";
inputs = { inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/24.05"; nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-parts.url = "github:hercules-ci/flake-parts";
rust-overlay = { rust-overlay = {
url = "github:oxalica/rust-overlay"; url = "github:oxalica/rust-overlay";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };
flake-utils.url = "github:numtide/flake-utils";
crate2nix = { crate2nix = {
url = "github:nix-community/crate2nix"; url = "github:nix-community/crate2nix";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };
}; };
nixConfig = {
extra-trusted-public-keys = "eigenvalue.cachix.org-1:ykerQDDa55PGxU25CETy9wF6uVDpadGGXYrFNJA3TUs=";
extra-substituters = "https://eigenvalue.cachix.org";
allow-import-from-derivation = true;
};
outputs = inputs @ { outputs = inputs @ {
crate2nix, flake-parts,
flake-utils,
nixpkgs,
rust-overlay, rust-overlay,
crate2nix,
... ...
}: }:
flake-utils.lib.eachDefaultSystem ( flake-parts.lib.mkFlake {inherit inputs;} (top: {
system: let systems = [
# Overlay pkgs with rust-bin "x86_64-linux"
overlays = [(import rust-overlay)]; ];
pkgs = import nixpkgs {
inherit system overlays;
};
perSystem = {
self',
pkgs,
system,
...
}: let
# Use rust-bin to generate the toolchain from rust-toolchain.toml # Use rust-bin to generate the toolchain from rust-toolchain.toml
rust-toolchain = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml; rust-toolchain = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
@@ -49,6 +45,22 @@
rav1e = attrs: { rav1e = attrs: {
CARGO_ENCODED_RUSTFLAGS = ""; CARGO_ENCODED_RUSTFLAGS = "";
}; };
# Fix thread 'main' (222) panicked at build.rs:250:45:
av-scenechange = attrs: {
CARGO_ENCODED_RUSTFLAGS = "";
};
# Bindgen fix
ffmpeg-sys-next = attrs: {
buildInputs = [pkgs.ffmpeg_4];
LIBCLANG_PATH = "${pkgs.llvmPackages.libclang.lib}/lib";
nativeBuildInputs = [pkgs.pkg-config];
BINDGEN_EXTRA_CLANG_ARGS = [
"--sysroot=${pkgs.glibc.dev}"
];
};
ffmpeg-next = attrs: {
features = ["codec" "format" "ffmpeg4" "ffmpeg_4_0" "ffmpeg_4_1" "ffmpeg_4_2" "ffmpeg_4_3" "ffmpeg_4_4" "ff_api_vaapi" "software-scaling" "software-resampling"];
};
}; };
}; };
@@ -61,37 +73,56 @@
cargoNix = import generatedCargoNix { cargoNix = import generatedCargoNix {
inherit pkgs buildRustCrateForPkgs; inherit pkgs buildRustCrateForPkgs;
}; };
in rec { in {
apps = rec { _module.args.pkgs = import inputs.nixpkgs {
inherit system;
overlays = [rust-overlay.overlays.default];
};
packages.lispers = cargoNix.workspaceMembers.lispers.build.overrideAttrs (attrs: {
preConfigure = ''
export LISPERS_OUT_DIR="$out"
export LISPERS_DONT_COPY_SCENES=1
'';
postInstall = ''
cp -r $src/scenes $out/scenes
'';
});
packages.default = self'.packages.lispers;
apps = {
lisp_demo = { lisp_demo = {
type = "app"; type = "app";
program = "${packages.default}/bin/lisp_demo"; program = "${self'.packages.lispers}/bin/lisp_demo";
}; };
repl = { repl = {
type = "app"; type = "app";
program = "${packages.default}/bin/repl"; program = "${self'.packages.lispers}/bin/repl";
}; };
rt_demo = { rt_demo = {
type = "app"; type = "app";
program = "${packages.default}/bin/rt_demo"; program = "${self'.packages.lispers}/bin/rt_demo";
}; };
rt_demo_lisp = { rt_lisp_demo = {
type = "app"; type = "app";
program = "${packages.default}/bin/rt_lisp_demo"; program = "${self'.packages.lispers}/bin/rt_lisp_demo";
}; };
rt_interp = { rt_interp = {
type = "app"; type = "app";
program = "${packages.default}/bin/rt_interp"; program = "${self'.packages.lispers}/bin/rt_interp";
}; };
default = rt_demo_lisp; default = self'.apps.rt_lisp_demo;
}; };
packages = rec {
lispers = cargoNix.workspaceMembers.lispers.build; devShells.default = pkgs.mkShell {
default = lispers; shellHook = ''
export LISPERS_USE_LOCAL_SCENES=1
'';
LIBCLANG_PATH = "${pkgs.llvmPackages.libclang.lib}/lib";
nativeBuildInputs = [rust-toolchain pkgs.pkg-config pkgs.ffmpeg_4];
BINDGEN_EXTRA_CLANG_ARGS = [
"--sysroot=${pkgs.glibc.dev}"
];
}; };
devShell = pkgs.mkShell {
buildInputs = [rust-toolchain];
}; };
} });
);
} }
+26 -2
View File
@@ -1,5 +1,5 @@
use super::{expression::Expression, prelude::mk_prelude}; use super::{expression::Expression, prelude::mk_prelude};
use std::{cell::RefCell, collections::HashMap, rc::Rc}; use std::{cell::RefCell, collections::HashMap, fmt::Display, rc::Rc};
#[derive(PartialEq, Clone, Debug)] #[derive(PartialEq, Clone, Debug)]
/// A Environment is a stack of `EnvironmentLayer`s. Each `EnvironmentLayer` is a mapping from /// A Environment is a stack of `EnvironmentLayer`s. Each `EnvironmentLayer` is a mapping from
@@ -64,7 +64,7 @@ impl<'a> Environment<'a> {
} }
/// Construct a new `Environment` with `self` as the outer `Environment`. /// Construct a new `Environment` with `self` as the outer `Environment`.
pub fn mk_inner(&self) -> Environment { pub fn mk_inner(&'a self) -> Environment<'a> {
Environment { Environment {
layer: EnvironmentLayer::new(), layer: EnvironmentLayer::new(),
outer: Some(self), outer: Some(self),
@@ -136,6 +136,30 @@ impl Default for Environment<'_> {
} }
} }
impl Display for Environment<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(x) = &self.outer {
write!(f, "{}<{}", self.layer, x)
} else {
write!(f, "")
}
}
}
impl Display for EnvironmentLayer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{{{}}}",
self.symbols
.iter()
.map(|(k, v)| format!("{}={}", k, v))
.collect::<Vec<_>>()
.join(", ")
)
}
}
#[test] #[test]
fn test_environment() { fn test_environment() {
let mut env = Environment::new(); let mut env = Environment::new();
+24 -2
View File
@@ -1,10 +1,12 @@
use std::fmt::Display; use std::fmt::Display;
use crate::parser::ParserError;
use super::environment::Environment; use super::environment::Environment;
use super::environment::EnvironmentLayer; use super::environment::EnvironmentLayer;
use super::expression::Expression; use super::expression::Expression;
#[derive(Debug)] #[derive(Debug, Clone, PartialEq)]
/// All possible evaluation errors /// All possible evaluation errors
pub enum EvalError { pub enum EvalError {
SymbolNotBound(String), SymbolNotBound(String),
@@ -14,6 +16,13 @@ pub enum EvalError {
TypeError(String), TypeError(String),
NotASymbol(Expression), NotASymbol(Expression),
RuntimeError(String), RuntimeError(String),
ParserError(ParserError),
}
impl From<ParserError> for EvalError {
fn from(value: ParserError) -> Self {
EvalError::ParserError(value)
}
} }
impl Display for EvalError { impl Display for EvalError {
@@ -26,6 +35,7 @@ impl Display for EvalError {
EvalError::TypeError(s) => write!(f, "Type error: {}", s), EvalError::TypeError(s) => write!(f, "Type error: {}", s),
EvalError::NotASymbol(e) => write!(f, "Expression {} is not a symbol", e), EvalError::NotASymbol(e) => write!(f, "Expression {} is not a symbol", e),
EvalError::RuntimeError(s) => write!(f, "Runtime error: {}", s), EvalError::RuntimeError(s) => write!(f, "Runtime error: {}", s),
EvalError::ParserError(s) => write!(f, "Parser error: {}", s),
} }
} }
} }
@@ -87,12 +97,23 @@ fn dispatch_anonymous_function(
} }
for (arg, symbol) in args.iter_mut().zip(argument_symbols.iter()) { for (arg, symbol) in args.iter_mut().zip(argument_symbols.iter()) {
overlay.set(symbol.to_owned(), eval(env, arg.to_owned())?); overlay.set(symbol.to_owned(), eval(&env, arg.to_owned())?);
} }
eval(&env.overlay(overlay), body) eval(&env.overlay(overlay), body)
} }
/// Dispatch a closure. Evaluates `body` in `env` overlayed with the `layer`
fn dispatch_closure(
env: &Environment,
layer: EnvironmentLayer,
body: Expression,
) -> Result<Expression, EvalError> {
println!("Dispatching closure: {} -> {}", &layer, &body);
let env = env.overlay(layer);
eval(&env, body)
}
/// Evaluate an expression inside an environment /// Evaluate an expression inside an environment
pub fn eval(env: &Environment, expr: Expression) -> Result<Expression, EvalError> { pub fn eval(env: &Environment, expr: Expression) -> Result<Expression, EvalError> {
match expr { match expr {
@@ -104,6 +125,7 @@ pub fn eval(env: &Environment, expr: Expression) -> Result<Expression, EvalError
} => dispatch_anonymous_function(env, argument_symbols, *body, *rhs), } => dispatch_anonymous_function(env, argument_symbols, *body, *rhs),
a => Err(EvalError::NotAFunction(a)), a => Err(EvalError::NotAFunction(a)),
}, },
Expression::Closure { layer, body } => dispatch_closure(env, layer, *body),
Expression::Quote(e) => Ok(*e), Expression::Quote(e) => Ok(*e),
Expression::Symbol(s) => env.get(&s).ok_or(EvalError::SymbolNotBound(s)), Expression::Symbol(s) => env.get(&s).ok_or(EvalError::SymbolNotBound(s)),
x => Ok(x), x => Ok(x),
+80 -2
View File
@@ -7,6 +7,7 @@ use std::ops::DerefMut;
use as_any::AsAny; use as_any::AsAny;
use super::environment::Environment; use super::environment::Environment;
use super::environment::EnvironmentLayer;
use super::eval::CellIterator; use super::eval::CellIterator;
use super::eval::EvalError; use super::eval::EvalError;
@@ -121,13 +122,17 @@ impl Display for ForeignDataStore {
} }
} }
#[derive(Clone, Debug, PartialEq, PartialOrd)] #[derive(Clone, Debug)]
/// A sum type of all possible lisp expressions. /// A sum type of all possible lisp expressions.
pub enum Expression { pub enum Expression {
/// The classic lisp cons cell aka (a . b) used to construct expressions. /// The classic lisp cons cell aka (a . b) used to construct expressions.
Cell(Box<Expression>, Box<Expression>), Cell(Box<Expression>, Box<Expression>),
/// A function expression pointing to native code. /// A function expression pointing to native code.
Function(fn(&Environment, Expression) -> Result<Expression, EvalError>), Function(fn(&Environment, Expression) -> Result<Expression, EvalError>),
Closure {
layer: EnvironmentLayer,
body: Box<Expression>,
},
/// A anonymous function expression consisting of bound symbols and a body expression. /// A anonymous function expression consisting of bound symbols and a body expression.
AnonymousFunction { AnonymousFunction {
argument_symbols: Vec<String>, argument_symbols: Vec<String>,
@@ -151,6 +156,64 @@ pub enum Expression {
Nil, Nil,
} }
impl PartialEq for Expression {
fn eq(&self, other: &Self) -> bool {
use Expression::*;
match (self, other) {
(Cell(a1, b1), Cell(a2, b2)) => PartialEq::eq(a1, a2) && PartialEq::eq(b1, b2),
(
AnonymousFunction {
argument_symbols: args1,
body: body1,
},
AnonymousFunction {
argument_symbols: args2,
body: body2,
},
) => PartialEq::eq(args1, args2) && PartialEq::eq(body1, body2),
(ForeignExpression(f1), ForeignExpression(f2)) => PartialEq::eq(f1, f2),
(Quote(e1), Quote(e2)) => PartialEq::eq(e1, e2),
(Symbol(s1), Symbol(s2)) => PartialEq::eq(s1, s2),
(Integer(i1), Integer(i2)) => PartialEq::eq(i1, i2),
(Float(f1), Float(f2)) => PartialEq::eq(f1, f2),
(String(s1), String(s2)) => PartialEq::eq(s1, s2),
(Nil, Nil) => true,
(True, True) => true,
_ => false,
}
}
}
impl PartialOrd for Expression {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
use Expression::*;
match (self, other) {
(Cell(a1, b1), Cell(a2, b2)) => a1.partial_cmp(a2).or_else(|| b1.partial_cmp(b2)),
(
AnonymousFunction {
argument_symbols: args1,
body: body1,
},
AnonymousFunction {
argument_symbols: args2,
body: body2,
},
) => args1
.partial_cmp(args2)
.or_else(|| body1.partial_cmp(body2)),
(ForeignExpression(f1), ForeignExpression(f2)) => f1.partial_cmp(f2),
(Quote(e1), Quote(e2)) => e1.partial_cmp(e2),
(Symbol(s1), Symbol(s2)) => s1.partial_cmp(s2),
(Float(f1), Float(f2)) => f1.partial_cmp(f2),
(Integer(i1), Integer(i2)) => i1.partial_cmp(i2),
(String(s1), String(s2)) => s1.partial_cmp(s2),
(Nil, Nil) => Some(std::cmp::Ordering::Equal),
(True, True) => Some(std::cmp::Ordering::Equal),
_ => None,
}
}
}
impl<T: ForeignData> From<ForeignDataWrapper<T>> for Expression { impl<T: ForeignData> From<ForeignDataWrapper<T>> for Expression {
fn from(value: ForeignDataWrapper<T>) -> Expression { fn from(value: ForeignDataWrapper<T>) -> Expression {
Expression::ForeignExpression(ForeignDataStore::new(value.0)) Expression::ForeignExpression(ForeignDataStore::new(value.0))
@@ -192,6 +255,18 @@ impl From<Vec<Expression>> for Expression {
} }
} }
impl<const N: usize> From<[Expression; N]> for Expression {
fn from(mut value: [Expression; N]) -> Self {
let mut current = Expression::Nil;
for e in value.iter_mut().rev() {
current = Expression::Cell(Box::new(e.to_owned()), Box::new(current));
}
current
}
}
impl From<(Expression, Expression)> for Expression { impl From<(Expression, Expression)> for Expression {
fn from(value: (Expression, Expression)) -> Self { fn from(value: (Expression, Expression)) -> Self {
Expression::Cell(Box::new(value.0), Box::new(value.1)) Expression::Cell(Box::new(value.0), Box::new(value.1))
@@ -341,6 +416,9 @@ impl Display for Expression {
} }
} }
Expression::Function(_) => write!(f, "<function>"), Expression::Function(_) => write!(f, "<function>"),
Expression::Closure { layer, body } => {
write!(f, "(closure captured={}, body={})", layer, body)
}
Expression::AnonymousFunction { Expression::AnonymousFunction {
argument_symbols, argument_symbols,
body, body,
@@ -349,7 +427,7 @@ impl Display for Expression {
Expression::Symbol(s) => write!(f, "{}", s), Expression::Symbol(s) => write!(f, "{}", s),
Expression::Integer(i) => write!(f, "{}", i), Expression::Integer(i) => write!(f, "{}", i),
Expression::Float(fl) => write!(f, "{}", fl), Expression::Float(fl) => write!(f, "{}", fl),
Expression::String(s) => write!(f, "{}", s), Expression::String(s) => write!(f, "\"{}\"", s),
Expression::True => write!(f, "true"), Expression::True => write!(f, "true"),
Expression::Nil => write!(f, "nil"), Expression::Nil => write!(f, "nil"),
} }
+68 -3
View File
@@ -1,3 +1,6 @@
use crate::parser::ExpressionStream;
use crate::parser::ParserError;
use super::environment::Environment; use super::environment::Environment;
use super::environment::EnvironmentLayer; use super::environment::EnvironmentLayer;
use super::eval::eval; use super::eval::eval;
@@ -5,6 +8,7 @@ use super::eval::CellIterator;
use super::eval::EvalError; use super::eval::EvalError;
use super::expression::Expression; use super::expression::Expression;
use std::collections::HashMap; use std::collections::HashMap;
use std::path::PathBuf;
pub fn prelude_add(env: &Environment, expr: Expression) -> Result<Expression, EvalError> { pub fn prelude_add(env: &Environment, expr: Expression) -> Result<Expression, EvalError> {
let [a, b] = expr.try_into()?; let [a, b] = expr.try_into()?;
@@ -144,13 +148,18 @@ pub fn prelude_let(env: &Environment, expr: Expression) -> Result<Expression, Ev
}) })
.collect::<Result<HashMap<String, Expression>, EvalError>>()?; .collect::<Result<HashMap<String, Expression>, EvalError>>()?;
eval(&env.overlay(bindings.into()), body) Ok(Expression::Closure {
layer: bindings.into(),
body: Box::new(body),
})
} }
pub fn prelude_if(env: &Environment, expr: Expression) -> Result<Expression, EvalError> { pub fn prelude_if(env: &Environment, expr: Expression) -> Result<Expression, EvalError> {
let [predicate, e_then, e_else] = expr.try_into()?; let [predicate, e_then, e_else]: [Expression; 3] = expr.try_into()?;
match eval(env, predicate)? { let val = eval(env, predicate.clone())?;
match val {
Expression::Nil => eval(env, e_else), Expression::Nil => eval(env, e_else),
_ => eval(env, e_then), _ => eval(env, e_then),
} }
@@ -325,6 +334,60 @@ pub fn prelude_to_string(env: &Environment, expr: Expression) -> Result<Expressi
Ok(Expression::String(format!("{}", eval(env, e)?))) Ok(Expression::String(format!("{}", eval(env, e)?)))
} }
pub fn prelude_load(env: &Environment, expr: Expression) -> Result<Expression, EvalError> {
let [expr] = expr.try_into()?;
let lisp_string: String = eval(env, expr)?.try_into()?;
let mut last_result = Expression::Nil;
for expr in ExpressionStream::from_char_stream(lisp_string.chars())
.collect::<Result<Vec<Expression>, ParserError>>()?
{
last_result = eval(env, expr)?;
}
Ok(last_result)
}
pub fn prelude_include(env: &Environment, expr: Expression) -> Result<Expression, EvalError> {
let [expr] = expr.try_into()?;
let lisp_file: String = eval(env, expr)?.try_into()?;
println!("From File: {}", env.get("FILE").unwrap());
// Try to resolve as relative to FILE
let resolved_lisp_file: PathBuf = PathBuf::from(
env.get("FILE")
.map(|x| x.try_into())
.unwrap_or(Ok(String::new()))?,
)
.canonicalize()
.map_err(|x| EvalError::RuntimeError(x.to_string()))?
.parent()
.ok_or(EvalError::RuntimeError(
"Could not get parent of current file.".to_string(),
))?
.join(&lisp_file);
println!("Loading {}", resolved_lisp_file.display());
let lisp_string = std::fs::read_to_string(&resolved_lisp_file)
.map_err(|e| EvalError::RuntimeError(e.to_string()))?;
// Use enviroment for resolved file or fallback to the lisp_file argument
let mut env = env.mk_inner();
env.set(
"FILE".to_string(),
resolved_lisp_file
.to_str()
.unwrap_or(&lisp_file)
.to_string()
.into(),
);
prelude_load(&env, [lisp_string.into()].into())
}
pub fn mk_prelude(layer: &mut EnvironmentLayer) { pub fn mk_prelude(layer: &mut EnvironmentLayer) {
layer.set("+".to_string(), Expression::Function(prelude_add)); layer.set("+".to_string(), Expression::Function(prelude_add));
layer.set("-".to_string(), Expression::Function(prelude_sub)); layer.set("-".to_string(), Expression::Function(prelude_sub));
@@ -355,4 +418,6 @@ pub fn mk_prelude(layer: &mut EnvironmentLayer) {
"to-string".to_string(), "to-string".to_string(),
Expression::Function(prelude_to_string), Expression::Function(prelude_to_string),
); );
layer.set("load".to_string(), Expression::Function(prelude_load));
layer.set("include".to_string(), Expression::Function(prelude_include));
} }
+13
View File
@@ -3,6 +3,7 @@ use super::tokenizer::tokenize;
use super::tokenizer::TokenStream; use super::tokenizer::TokenStream;
use super::tokenizer::TokenizerError; use super::tokenizer::TokenizerError;
use crate::lisp::Expression; use crate::lisp::Expression;
use std::fmt::Display;
use std::iter::Peekable; use std::iter::Peekable;
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
@@ -10,6 +11,7 @@ pub enum ParserError {
UnexpectedToken(Token), UnexpectedToken(Token),
TokenizerError(TokenizerError), TokenizerError(TokenizerError),
UnexpectedEndOfInput, UnexpectedEndOfInput,
RuntimeError(String),
} }
impl From<TokenizerError> for ParserError { impl From<TokenizerError> for ParserError {
@@ -18,6 +20,17 @@ impl From<TokenizerError> for ParserError {
} }
} }
impl Display for ParserError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
ParserError::TokenizerError(t) => write!(f, "Tokenizer Error: {}", t),
ParserError::UnexpectedToken(t) => write!(f, "Unexpecte Token: {}", t),
ParserError::UnexpectedEndOfInput => write!(f, "Unexpected end of input."),
ParserError::RuntimeError(s) => write!(f, "{}", s),
}
}
}
fn parse_list<I>(stream: &mut Peekable<TokenStream<I>>) -> Result<Expression, ParserError> fn parse_list<I>(stream: &mut Peekable<TokenStream<I>>) -> Result<Expression, ParserError>
where where
I: Iterator<Item = char>, I: Iterator<Item = char>,
+19
View File
@@ -1,3 +1,5 @@
use std::fmt::Display;
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
/// Sum type of different tokens /// Sum type of different tokens
pub enum Token { pub enum Token {
@@ -12,3 +14,20 @@ pub enum Token {
Symbol(String), Symbol(String),
True, True,
} }
impl Display for Token {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Token::FloatLiteral(x) => write!(f, "{}", x),
Token::IntLiteral(x) => write!(f, "{}", x),
Token::Dot => write!(f, "."),
Token::Nil => write!(f, "nil"),
Token::ParClose => write!(f, ")"),
Token::ParOpen => write!(f, "("),
Token::Quote => write!(f, "'"),
Token::StringLiteral(x) => write!(f, "\"{}\"", x),
Token::Symbol(x) => write!(f, "{}", x),
Token::True => write!(f, "true"),
}
}
}
+23 -6
View File
@@ -1,3 +1,5 @@
use std::fmt::Display;
use super::token::Token; use super::token::Token;
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
@@ -7,6 +9,14 @@ pub enum TokenizerError {
UnmatchedSequence(String), UnmatchedSequence(String),
} }
impl Display for TokenizerError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TokenizerError::UnmatchedSequence(s) => write!(f, "Unmatched sequence: {}", s),
}
}
}
/// A reader used to wrap the `TokenStream`. /// A reader used to wrap the `TokenStream`.
/// When reading, it starts with the staging buffer of the stream, once /// When reading, it starts with the staging buffer of the stream, once
/// it's end is reached, the input stream is copied character wise to /// it's end is reached, the input stream is copied character wise to
@@ -294,7 +304,9 @@ where
let mut buf = String::new(); let mut buf = String::new();
while let Some(c) = reader.next() { while let Some(c) = reader.next() {
if c.is_ascii_digit() { if buf.is_empty() && c == '-' {
buf.push(c);
} else if c.is_ascii_digit() {
buf.push(c); buf.push(c);
} else { } else {
reader.step_back(1); reader.step_back(1);
@@ -317,7 +329,9 @@ where
let mut has_dot = false; let mut has_dot = false;
while let Some(c) = reader.next() { while let Some(c) = reader.next() {
if c.is_ascii_digit() { if buf.is_empty() && c == '-' {
buf.push(c);
} else if c.is_ascii_digit() {
buf.push(c); buf.push(c);
} else if c == '.' && !has_dot { } else if c == '.' && !has_dot {
buf.push(c); buf.push(c);
@@ -337,11 +351,12 @@ where
#[test] #[test]
fn test_tokenize() { fn test_tokenize() {
let test_str = "(\"abcdefg( )123\" )(\n\t 'nil true \"true\")00987463 123.125 . 0+-*/go="; let test_str =
"(\"abcdefg( )123\" )(\n\t 'nil true \"true\")00987463 123.125 -20 -3.14 . 0+-*/go=";
let result: Vec<_> = tokenize(&mut test_str.chars()).collect(); let result: Vec<_> = tokenize(&mut test_str.chars()).collect();
assert_eq!(result.len(), 13); assert_eq!(result.len(), 15);
assert_eq!(result[0].clone().unwrap(), Token::ParOpen); assert_eq!(result[0].clone().unwrap(), Token::ParOpen);
assert_eq!( assert_eq!(
result[1].clone().unwrap(), result[1].clone().unwrap(),
@@ -359,9 +374,11 @@ fn test_tokenize() {
assert_eq!(result[8].clone().unwrap(), Token::ParClose); assert_eq!(result[8].clone().unwrap(), Token::ParClose);
assert_eq!(result[9].clone().unwrap(), Token::IntLiteral(987463)); assert_eq!(result[9].clone().unwrap(), Token::IntLiteral(987463));
assert_eq!(result[10].clone().unwrap(), Token::FloatLiteral(123.125)); assert_eq!(result[10].clone().unwrap(), Token::FloatLiteral(123.125));
assert_eq!(result[11].clone().unwrap(), Token::Dot); assert_eq!(result[11].clone().unwrap(), Token::IntLiteral(-20));
assert_eq!(result[12].clone().unwrap(), Token::FloatLiteral(-3.14));
assert_eq!(result[13].clone().unwrap(), Token::Dot);
assert_eq!( assert_eq!(
result[12].clone().unwrap(), result[14].clone().unwrap(),
Token::Symbol("0+-*/go=".to_string()) Token::Symbol("0+-*/go=".to_string())
); );
} }
+3 -3
View File
@@ -7,6 +7,6 @@ edition = "2021"
proc-macro = true proc-macro = true
[dependencies] [dependencies]
proc-macro2 = "1.0.92" proc-macro2 = "1.0.106"
quote = "1.0.38" quote = "1.0.45"
syn = "2.0.94" syn = "2.0.117"
+2 -1
View File
@@ -179,13 +179,14 @@ pub fn native_lisp_function_proxy(item: TokenStream) -> TokenStream {
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let fname_str = fname.to_string();
quote! { quote! {
fn #fname(env: &Environment, expr: Expression) -> Result<Expression, EvalError> { fn #fname(env: &Environment, expr: Expression) -> Result<Expression, EvalError> {
#eval_statement #eval_statement
#(#try_apply_statements)* #(#try_apply_statements)*
Err(EvalError::TypeError("No applicable method found".to_string())) Err(EvalError::TypeError(format!("Could not call {} with arguments {} ", #fname_str, expr).to_string()))
} }
} }
.into() .into()
+1 -1
View File
@@ -1,3 +1,3 @@
[toolchain] [toolchain]
channel = "1.80.1" channel = "1.93.1"
components = [ "rustfmt", "rustc-dev", "rust-analyzer", "rust-src"] components = [ "rustfmt", "rustc-dev", "rust-analyzer", "rust-src"]
+67
View File
@@ -0,0 +1,67 @@
(include "./materials.lisp")
(set 's1
(sphere
(point 0 1 0) 1 blue))
(set 's2
(sphere
(point 2 0.5 2) 0.5 green))
(set 'mirror-dome
(sphere
(point 0 -17 0)
30 dark-mirror))
(defun spiral-sphere (i n t)
(sphere
(progn
(point
(* 2 (cos (/ (* i 6.2) n)))
(+ 0.5 (* 0.3 (cos (+ (/ (* i 6.2) n) (/ t 5.0)))))
(* 2 (sin (/ (* i 6.2) n))))
)
0.2 red))
(defun spiral (scn i n t)
(if (< i n)
(scene-add
(spiral scn (+ i 1) n t)
(spiral-sphere i n t))
scn))
(set 'p1
(checkerboard
(point 0 0 0)
(vector 0 1 0)
black white 0.5
(vector 0.5 0 1)))
(set 'l1 (light (point 3 10 5) (color 1 1 1)))
(set 'l2 (light (point 2 10 5) (color 1 1 1)))
(set 'scn-base (scene
(color 0.1 0.1 0.1)
'(s1 s2 p1 mirror-dome)
'(l1 l2)))
(set 'cam (camera (point 0 3 6) (point 0 0 0) (vector 0 1 0) 40 1920 1080))
(defun scene-fn (t)
(spiral scn-base 0 30 t))
(defun cam-fn (t c)
(let '((pos . (point -3 0.5 8))
(cnt . (point 0 0 0))
(to . (point -3 0.5 -8))
(up . (vector 0 1 0))
(fovy . 80)
(pct . (/ t 300.0)))
(let '((tpos . (+ pos (* (- to pos) pct)))
(tfovy . (+ fovy (* 40 pct)))
)
(camera-reposition c tpos cnt up tfovy)
)
))
(render-animation cam "demo-animation.mp4" scene-fn cam-fn 400 30 7 2)
+69
View File
@@ -0,0 +1,69 @@
(set 'red
(material
(color 1 0 0)
(color 1 0 0)
(color 0.5 0 0)
50 0.25))
(set 'blue
(material
(color 0 0 1)
(color 0 0 1)
(color 0 0 0.6)
50 0.25))
(set 'green
(material
(color 0 1 0)
(color 0 1 0)
(color 0 0.6 0)
50 0.25))
(set 'mandelbrot-red
(mandelbrot-texture
1800.0
(point2 -0.7489967346191402 -0.06952285766601607)
1000
(color 0.3 0 0)
(color 0.3 0 0)
(color 0.3 0 0)
))
(set 'mandelbrot-blue
(mandelbrot-texture
1800.0
(point2 -0.7489967346191402 -0.06952285766601607)
1000
(color 0 0 0.3)
(color 0 0 0.3)
(color 0 0 0.3)
))
(set 's1
(sphere
(point 0 1 0) 1 blue))
(set 's2
(sphere
(point 2 0.5 2) 0.5 green))
(set 'p1
(texture-plane
mandelbrot-red
(point 0 0 0)
(vector 0 1 0)
1.0
(vector 1 0 0)))
(set 'l1 (light (point 3 10 5) (color 1 1 1)))
(set 'l2 (light (point 2 10 5) (color 1 1 1)))
(set 'scn (scene
(color 0.1 0.1 0.1)
'(s1 s2 p1)
'(l1 l2)))
(println (cons "Final Scene:" scn))
(set 'cam (camera (point 0 3 6) (point 0 0 0) (vector 0 1 0) 40 1920 1080))
(render cam scn 5 4 "demo-3.png")
+47
View File
@@ -0,0 +1,47 @@
(include "./materials.lisp")
(set 'frames 300)
(set 'fps 30)
(set 'ray-depth 5)
(set 'sub-pixel 2)
(set 'mz (mandel-zoom frames 1800 5000))
(defun mandel-plane (t)
(texture-plane
(mz t)
(point 0 0 0)
(vector 0 1 0)
1.0
(vector 1 0 0)
))
(set 'l1 (light (point 3 10 5) (color 1 1 1)))
(set 'l2 (light (point 2 10 5) (color 1 1 1)))
(defun scn (t)
(scene
(color 0.1 0.1 0.1)
'((mandel-plane t))
'(l1 l2)
))
(defun cam (t c) c)
(set 'base-cam
(camera
(point 0 3 6)
(point 0 0 0)
(vector 0 1 0)
40 1920 1080))
(render-animation
base-cam
"demo-4-animation.mp4"
scn
cam
frames
fps
ray-depth
sub-pixel)
+66
View File
@@ -0,0 +1,66 @@
(set 'red
(material
(color 1 0 0)
(color 1 0 0)
(color 0.5 0 0)
50 0.25))
(set 'blue
(material
(color 0 0 1)
(color 0 0 1)
(color 0 0 0.6)
50 0.25))
(set 'green
(material
(color 0 1 0)
(color 0 1 0)
(color 0 0.6 0)
50 0.25))
(set 'white
(material
(color 1 1 1)
(color 1 1 1)
(color 0.6 0.6 0.6)
100 0.4))
(set 'black
(material
(color 0 0 0)
(color 0 0 0)
(color 0.6 0.6 0.6)
100 0.4))
(set 'dark-mirror
(material
(color 0.01 0.05 0.15)
(color 0.01 0.05 0.15)
(color 0.01 0.05 0.15)
20 0.7))
(set 'mandelbrot-red
(mandelbrot-texture
1800.0
(point2 -0.7489967346191402 -0.06952285766601607)
1000
(color 0.3 0 0)
(color 0.3 0 0)
(color 0.3 0 0)
))
(defun mandel-zoom (n z1 z2)
(let '((n . n)
(z1 . z1)
(z2 . z2))
(lambda (t)
(let '((pct . (/ t (* n 1.0)))
(range . (- z2 z1)))
(mandelbrot-texture
(+ z1 (* pct range))
(point2 -0.7489967346191402 -0.06952285766601607)
1000
(color 0.3 0 0)
(color 0.3 0 0)
(color 0.3 0 0)
)
)))
)
+12 -2
View File
@@ -8,13 +8,20 @@ fn main() {
"(print myvar) (print 'myvar)", "(print myvar) (print 'myvar)",
"(car (cons 'a 'b)) (cdr (cons 'c 'd)) (cons 'a 'b)", "(car (cons 'a 'b)) (cdr (cons 'c 'd)) (cons 'a 'b)",
"(eval (car (cons 'myvar 'b)))", "(eval (car (cons 'myvar 'b)))",
"(defun test (i) (progn (println i) (* 2 i)))",
"(test 10)",
"(set 'pow (lambda (a b) (if (= b 0) 1 (* a (pow a (- b 1))))))", "(set 'pow (lambda (a b) (if (= b 0) 1 (* a (pow a (- b 1))))))",
"pow", "pow",
"(pow 2 10)", "(pow 2 10)",
"(let '((fib . (lambda (n) (if (< n 2) n (+ (fib (- n 1)) (fib (- n 2))))))) (fib 10))", "(let '((fib . (lambda (n) (if (< n 2) n (+ (fib (- n 1)) (fib (- n 2))))))) (fib 10))",
"(defun do-n-times (f n) (if (= n 0) '() (cons (f) (do-n-times f (- n 1)))))", "(defun do-n-times (f n) (if (= n 0) '() (cons (f) (do-n-times f (- n 1)))))",
"(do-n-times (lambda () (print 'hello)) 5)", "(do-n-times (lambda () (println 'hello)) 5)",
"(progn (print 'hello) (print 'world))", "(progn (print 'hello) (print 'world))",
"(load \"(defun loaded-foo (x) (+ x 1))\")",
"(loaded-foo 1)",
"(defun bind-test (x) (let '((x . x)) (lambda (y) (+ x y))))",
"(set 'add10 (bind-test 10))",
"(add10 10)",
]; ];
let environment = Environment::default(); let environment = Environment::default();
@@ -29,7 +36,10 @@ fn main() {
println!("Evaluating: {}", expr.clone()); println!("Evaluating: {}", expr.clone());
match eval(&environment, expr) { match eval(&environment, expr) {
Ok(e) => println!("=> {}", e), Ok(e) => println!("=> {}", e),
Err(e) => println!("Error: {}", e), Err(e) => {
println!("Error: {}", e);
return;
}
} }
} }
} }
+12 -5
View File
@@ -17,14 +17,20 @@ fn main() {
mk_prelude(&mut layer); mk_prelude(&mut layer);
mk_raytrace(&mut layer); mk_raytrace(&mut layer);
let environment = Environment::from_layer(layer); let mut environment = Environment::from_layer(layer);
for (i, r) in for (program, path) in programs.iter().zip(program_paths) {
ExpressionStream::from_char_stream(programs.iter().map(|p| p.chars()).flatten()).enumerate() environment.set("FILE".to_string(), path.clone().into());
{
for (i, r) in ExpressionStream::from_char_stream(program.chars()).enumerate() {
match r { match r {
Err(err) => { Err(err) => {
println!("ParserError in Expression {}: {:?}", i + 1, err); println!(
"ParserError in File {} Expression {}: {:?}",
path,
i + 1,
err
);
break; break;
} }
Ok(expr) => match eval(&environment, expr) { Ok(expr) => match eval(&environment, expr) {
@@ -33,6 +39,7 @@ fn main() {
}, },
} }
} }
}
println!("Interpreter Done!"); println!("Interpreter Done!");
} }
+51 -24
View File
@@ -1,47 +1,74 @@
use std::collections::HashMap;
use std::path::Path;
use lispers::raytracer::lisp::mk_raytrace; use lispers::raytracer::lisp::mk_raytrace;
use lispers_core::lisp::environment::EnvironmentLayer; use lispers_core::lisp::environment::EnvironmentLayer;
use lispers_core::lisp::prelude::mk_prelude; use lispers_core::lisp::prelude::mk_prelude;
use lispers_core::lisp::{eval, Environment}; use lispers_core::lisp::{eval, Environment};
use lispers_core::parser::ExpressionStream; use lispers_core::parser::ExpressionStream;
const SCENES_DIR: &str = env!("SCENES_DIR");
fn main() { fn main() {
let programs = [ println!("Loading scenes from directory: {}", SCENES_DIR);
"(set 'red (material (color 1 0 0) (color 1 0 0) (color 0.5 0 0) 50 0.25))",
"(set 'blue (material (color 0 0 1) (color 0 0 1) (color 0 0 0.6) 50 0.25))", let mut scenes = HashMap::new();
"(set 'green (material (color 0 1 0) (color 0 1 0) (color 0 0.6 0) 50 0.25))", for e in std::fs::read_dir(Path::new(SCENES_DIR)).expect("Failed to read scenes directory") {
"(set 'white (material (color 1 1 1) (color 1 1 1) (color 0.6 0.6 0.6) 100 0.5))", let e = e.expect("Failed to read scene file");
"(set 'black (material (color 0 0 0) (color 0 0 0) (color 0.6 0.6 0.6) 100 0.5))", let t = e.file_type().expect("Failed to read scene file type");
"(set 's1 (sphere (point 0 1 0) 1 blue))", let n = e
"(set 's2 (sphere (point 2 0.5 2) 0.5 green))", .file_name()
"(defun spiral-sphere (i n) (sphere (print (point (* 2 (cos (/ (* i 6.2) n))) 0.5 (* 2 (sin (/ (* i 6.2) n))))) 0.5 red))", .into_string()
"(defun spiral (scn i n) (if (< i n) (scene-add (spiral scn (+ i 1) n) (spiral-sphere i n)) scn))", .expect("Failed to read scene file name");
"(set 'p1 (checkerboard (point 0 0 0) (vector 0 1 0) black white 0.5 (vector 0.5 0 1)))", if t.is_file() && n.starts_with("demo-") && n.ends_with(".lisp") {
"(set 'l1 (light (point 3 10 5) (color 1 1 1)))", scenes.insert(n, e);
"(set 'l2 (light (point 2 10 5) (color 1 1 1)))", }
"(set 'scn (scene (color 0.1 0.1 0.1) '(s1 s2 p1) '(l1 l2)))", }
"(set 'scn (spiral scn 0.0 10.0))",
"(print scn)", let args: Vec<_> = std::env::args().collect();
"(set 'cam (camera (point 0 3 6) (point 0 0 0) (vector 0 1 0) 40 1920 1080))",
"(render cam scn 5 4 \"rt-lisp-demo.png\")", if args.len() != 2 {
]; println!("Usage: {} <scene-file.lisp>", args[0]);
println!("Available scene files:");
for name in scenes.keys() {
println!(" {}", name);
}
return;
}
let mut layer = EnvironmentLayer::new(); let mut layer = EnvironmentLayer::new();
mk_prelude(&mut layer); mk_prelude(&mut layer);
mk_raytrace(&mut layer); mk_raytrace(&mut layer);
let mut environment = Environment::from_layer(layer);
environment.set(
"FILE".to_string(),
scenes
.get(&args[1])
.expect("Scene file not found")
.path()
.display()
.to_string()
.into(),
);
let environment = Environment::from_layer(layer); for r in ExpressionStream::from_char_stream(
std::fs::read_to_string(scenes.get(&args[1]).expect("Scene file not found").path())
for r in ExpressionStream::from_char_stream(programs.iter().map(|p| p.chars()).flatten()) { .expect("Failed to read scene file")
.chars(),
) {
match r { match r {
Err(err) => { Err(err) => {
println!("ParserError: {:?}", err); println!("ParserError: {:?}", err);
break; return;
} }
Ok(expr) => { Ok(expr) => {
println!("Evaluating: {}", expr.clone()); println!("Evaluating: {}", expr.clone());
match eval(&environment, expr) { match eval(&environment, expr) {
Ok(e) => println!("=> {}", e), Ok(e) => println!("=> {}", e),
Err(e) => println!("Error: {}", e), Err(e) => {
println!("Error: {}", e);
return;
}
} }
} }
} }
+58 -1
View File
@@ -1,11 +1,15 @@
use std::fmt::Display; use std::{fmt::Display, path::Path};
use super::{ use super::{
scene::Scene, scene::Scene,
types::{Color, Point3, Ray, Scalar, Vector3}, types::{Color, Point3, Ray, Scalar, Vector3},
RTError,
}; };
use image::RgbImage; use image::RgbImage;
use lispers_core::lisp::eval::EvalError;
use ndarray::Array3;
use rayon::prelude::*; use rayon::prelude::*;
use video_rs::{encode::Settings, Encoder, Time};
/// A camera that can render a scene. /// A camera that can render a scene.
#[derive(Clone, PartialEq, Debug)] #[derive(Clone, PartialEq, Debug)]
@@ -108,6 +112,59 @@ impl Camera {
}); });
img img
} }
pub fn reposition(
&self,
position: Point3,
center: Point3,
up: Vector3,
fovy: Scalar,
) -> Camera {
Camera::new(position, center, up, fovy, self.width, self.height)
}
pub fn render_animation<
SFn: Fn(u32) -> Result<Scene, EvalError>,
CFn: Fn(u32, &Camera) -> Result<Camera, EvalError>,
>(
&self,
path: &Path,
scene_fn: SFn,
update_cam: CFn,
frames: u32,
fps: u32,
depth: u32,
subp: u32,
) -> Result<(), RTError> {
let mut encoder = Encoder::new(
path,
Settings::preset_h264_yuv420p(self.width, self.height, false),
)?;
let frame_duration = Time::from_nth_of_a_second(fps as usize);
let mut timestamp = Time::zero();
let mut cam = self.to_owned();
for t in 0..frames {
println!(
"Rendering frame {}/{} for {}",
t + 1,
frames,
path.display()
);
cam = update_cam(t, &cam)?;
let img = cam.render(&scene_fn(t)?, depth, subp);
let frame = Array3::from_shape_fn((self.height, self.width, 3), |(y, x, c)| {
img.get_pixel(x as u32, y as u32)[c]
});
encoder.encode(&frame, timestamp)?;
timestamp = timestamp.aligned_with(frame_duration).add();
}
encoder.finish()?;
Ok(())
}
} }
impl Display for Camera { impl Display for Camera {
+317 -16
View File
@@ -1,4 +1,11 @@
use crate::raytracer::{scene::Scene, types::Light}; use std::path::PathBuf;
use crate::raytracer::{
scene::Scene,
sphere::TextureSphere,
texture::TextureWrapper,
types::{Light, Point2},
};
use lispers_macro::{native_lisp_function, native_lisp_function_proxy}; use lispers_macro::{native_lisp_function, native_lisp_function_proxy};
@@ -11,9 +18,11 @@ use lispers_core::lisp::{
use super::{ use super::{
camera::Camera, camera::Camera,
plane::{Checkerboard, Plane}, plane::{Checkerboard, Plane, TexturePlane},
sphere::Sphere, sphere::Sphere,
texture::MandelbrotTexture,
types::{Color, Material, Point3, RTObjectWrapper, Vector3}, types::{Color, Material, Point3, RTObjectWrapper, Vector3},
RTError,
}; };
#[native_lisp_function(eval)] #[native_lisp_function(eval)]
@@ -21,6 +30,11 @@ pub fn point(x: f64, y: f64, z: f64) -> Result<ForeignDataWrapper<Point3>, EvalE
Ok(ForeignDataWrapper::new(Point3::new(x, y, z))) Ok(ForeignDataWrapper::new(Point3::new(x, y, z)))
} }
#[native_lisp_function(eval)]
pub fn point2(x: f64, y: f64) -> Result<ForeignDataWrapper<Point2>, EvalError> {
Ok(ForeignDataWrapper::new(Point2::new(x, y)))
}
#[native_lisp_function(eval)] #[native_lisp_function(eval)]
pub fn vector(x: f64, y: f64, z: f64) -> Result<ForeignDataWrapper<Vector3>, EvalError> { pub fn vector(x: f64, y: f64, z: f64) -> Result<ForeignDataWrapper<Vector3>, EvalError> {
Ok(ForeignDataWrapper::new(Vector3::new(x, y, z))) Ok(ForeignDataWrapper::new(Vector3::new(x, y, z)))
@@ -61,6 +75,22 @@ pub fn sphere(
Ok(ForeignDataWrapper::new(RTObjectWrapper::from(Sphere::new(*pos, rad, *mat))).into()) Ok(ForeignDataWrapper::new(RTObjectWrapper::from(Sphere::new(*pos, rad, *mat))).into())
} }
#[native_lisp_function(eval)]
pub fn texture_sphere(
pos: ForeignDataWrapper<Point3>,
rad: f64,
tex: ForeignDataWrapper<TextureWrapper>,
) -> Result<ForeignDataWrapper<RTObjectWrapper>, EvalError> {
Ok(
ForeignDataWrapper::new(RTObjectWrapper::from(TextureSphere::new(
*pos,
rad,
tex.clone(),
)))
.into(),
)
}
#[native_lisp_function(eval)] #[native_lisp_function(eval)]
pub fn plane( pub fn plane(
pos: ForeignDataWrapper<Point3>, pos: ForeignDataWrapper<Point3>,
@@ -87,6 +117,47 @@ pub fn checkerboard(
) )
} }
#[native_lisp_function(eval)]
pub fn texture_plane(
texture: ForeignDataWrapper<TextureWrapper>,
pos: ForeignDataWrapper<Point3>,
norm: ForeignDataWrapper<Vector3>,
sca: f64,
up: ForeignDataWrapper<Vector3>,
) -> Result<ForeignDataWrapper<RTObjectWrapper>, EvalError> {
Ok(
ForeignDataWrapper::new(RTObjectWrapper::from(TexturePlane::new(
*pos,
*norm,
texture.clone(),
sca,
*up,
)))
.into(),
)
}
#[native_lisp_function(eval)]
pub fn mandelbrot_texture(
scale: f64,
at: ForeignDataWrapper<Point2>,
max_iter: i64,
ambient_color: ForeignDataWrapper<Color>,
diffuse_color: ForeignDataWrapper<Color>,
specular_color: ForeignDataWrapper<Color>,
) -> Result<ForeignDataWrapper<TextureWrapper>, EvalError> {
Ok(ForeignDataWrapper::new(TextureWrapper::new(
MandelbrotTexture::new(
scale,
*at,
max_iter as u32,
*ambient_color,
*diffuse_color,
*specular_color,
),
)))
}
pub fn scene(env: &Environment, expr: Expression) -> Result<Expression, EvalError> { pub fn scene(env: &Environment, expr: Expression) -> Result<Expression, EvalError> {
let [amb, objs, lgts]: [Expression; 3] = expr.try_into()?; let [amb, objs, lgts]: [Expression; 3] = expr.try_into()?;
@@ -148,6 +219,19 @@ pub fn camera(
))) )))
} }
#[native_lisp_function(eval)]
pub fn camera_reposition(
cam: ForeignDataWrapper<Camera>,
pos: ForeignDataWrapper<Point3>,
cnt: ForeignDataWrapper<Point3>,
up: ForeignDataWrapper<Vector3>,
fovy: f64,
) -> Result<ForeignDataWrapper<Camera>, EvalError> {
Ok(ForeignDataWrapper::new(
cam.to_owned().reposition(*pos, *cnt, *up, fovy),
))
}
#[native_lisp_function(eval)] #[native_lisp_function(eval)]
pub fn render( pub fn render(
cam: ForeignDataWrapper<Camera>, cam: ForeignDataWrapper<Camera>,
@@ -165,6 +249,47 @@ pub fn render(
} }
} }
pub fn render_animation(env: &Environment, expr: Expression) -> Result<Expression, EvalError> {
let [cam, path, scene_fn, update_cam, frames, fps, depth, subp]: [Expression; 8] =
expr.try_into()?;
let cam: ForeignDataWrapper<Camera> = eval(env, cam)?.try_into()?;
let path: String = eval(env, path)?.try_into()?;
let frames: i64 = eval(env, frames)?.try_into()?;
let fps: i64 = eval(env, fps)?.try_into()?;
let depth: i64 = eval(env, depth)?.try_into()?;
let subp: i64 = eval(env, subp)?.try_into()?;
let sfn = |t: u32| -> Result<Scene, EvalError> {
let scene_fn_call: Expression = [scene_fn.clone(), (t as i64).into()].into();
let scn: ForeignDataWrapper<Scene> = eval(env, scene_fn_call)?.try_into()?;
Ok(scn.to_owned())
};
let ucm = |t: u32, c: &Camera| -> Result<Camera, EvalError> {
let c = ForeignDataWrapper::new(c.to_owned());
let update_cam_call: Expression = [update_cam.clone(), (t as i64).into(), c.into()].into();
let new_c: ForeignDataWrapper<Camera> = eval(env, update_cam_call)?.try_into()?;
Ok(new_c.to_owned())
};
let path: PathBuf = path.into();
match cam.render_animation(
&path,
sfn,
ucm,
frames as u32,
fps as u32,
depth as u32,
subp as u32,
) {
Ok(()) => Ok(Expression::Nil),
Err(RTError::EvalError(e)) => Err(e),
Err(RTError::FFMpegError(e)) => Err(EvalError::RuntimeError(e.to_string())),
}
}
#[native_lisp_function(eval)] #[native_lisp_function(eval)]
pub fn sin(x: f64) -> Result<f64, EvalError> { pub fn sin(x: f64) -> Result<f64, EvalError> {
Ok(x.sin()) Ok(x.sin())
@@ -175,6 +300,16 @@ pub fn cos(x: f64) -> Result<f64, EvalError> {
Ok(x.cos()) Ok(x.cos())
} }
#[native_lisp_function(eval)]
pub fn add_i(x: i64, y: i64) -> Result<i64, EvalError> {
Ok(x + y)
}
#[native_lisp_function(eval)]
pub fn add_f(x: f64, y: f64) -> Result<f64, EvalError> {
Ok(x + y)
}
#[native_lisp_function] #[native_lisp_function]
pub fn vadd_vv( pub fn vadd_vv(
a: ForeignDataWrapper<Vector3>, a: ForeignDataWrapper<Vector3>,
@@ -200,15 +335,27 @@ pub fn vadd_pv(
} }
native_lisp_function_proxy!( native_lisp_function_proxy!(
fname = vadd, fname = add,
eval, eval,
dispatch = add_i,
dispatch = add_f,
dispatch = vadd_vv, dispatch = vadd_vv,
dispatch = vadd_vp, dispatch = vadd_vp,
dispatch = vadd_pv dispatch = vadd_pv
); );
#[native_lisp_function(eval)]
pub fn sub_i(x: i64, y: i64) -> Result<i64, EvalError> {
Ok(x - y)
}
#[native_lisp_function(eval)]
pub fn sub_f(x: f64, y: f64) -> Result<f64, EvalError> {
Ok(x - y)
}
#[native_lisp_function] #[native_lisp_function]
pub fn vsub_vv( pub fn sub_vv(
a: ForeignDataWrapper<Vector3>, a: ForeignDataWrapper<Vector3>,
b: ForeignDataWrapper<Vector3>, b: ForeignDataWrapper<Vector3>,
) -> Result<ForeignDataWrapper<Vector3>, EvalError> { ) -> Result<ForeignDataWrapper<Vector3>, EvalError> {
@@ -216,7 +363,7 @@ pub fn vsub_vv(
} }
#[native_lisp_function] #[native_lisp_function]
pub fn vsub_vp( pub fn sub_vp(
a: ForeignDataWrapper<Vector3>, a: ForeignDataWrapper<Vector3>,
b: ForeignDataWrapper<Point3>, b: ForeignDataWrapper<Point3>,
) -> Result<ForeignDataWrapper<Point3>, EvalError> { ) -> Result<ForeignDataWrapper<Point3>, EvalError> {
@@ -224,23 +371,44 @@ pub fn vsub_vp(
} }
#[native_lisp_function] #[native_lisp_function]
pub fn vsub_pv( pub fn sub_pv(
a: ForeignDataWrapper<Point3>, a: ForeignDataWrapper<Point3>,
b: ForeignDataWrapper<Vector3>, b: ForeignDataWrapper<Vector3>,
) -> Result<ForeignDataWrapper<Point3>, EvalError> { ) -> Result<ForeignDataWrapper<Point3>, EvalError> {
Ok(ForeignDataWrapper::new(*a - *b)) Ok(ForeignDataWrapper::new(*a - *b))
} }
#[native_lisp_function]
pub fn sub_pp(
a: ForeignDataWrapper<Point3>,
b: ForeignDataWrapper<Point3>,
) -> Result<ForeignDataWrapper<Vector3>, EvalError> {
Ok(ForeignDataWrapper::new(*a - *b))
}
native_lisp_function_proxy!( native_lisp_function_proxy!(
fname = vsub, fname = sub,
eval, eval,
dispatch = vsub_vv, dispatch = sub_i,
dispatch = vsub_vp, dispatch = sub_f,
dispatch = vsub_pv dispatch = sub_vv,
dispatch = sub_vp,
dispatch = sub_pv,
dispatch = sub_pp
); );
#[native_lisp_function(eval)]
pub fn mul_i(x: i64, y: i64) -> Result<i64, EvalError> {
Ok(x * y)
}
#[native_lisp_function(eval)]
pub fn mul_f(x: f64, y: f64) -> Result<f64, EvalError> {
Ok(x * y)
}
#[native_lisp_function] #[native_lisp_function]
pub fn vmul_vs( pub fn mul_vs(
a: ForeignDataWrapper<Vector3>, a: ForeignDataWrapper<Vector3>,
b: f64, b: f64,
) -> Result<ForeignDataWrapper<Vector3>, EvalError> { ) -> Result<ForeignDataWrapper<Vector3>, EvalError> {
@@ -248,18 +416,128 @@ pub fn vmul_vs(
} }
#[native_lisp_function] #[native_lisp_function]
pub fn vmul_sv( pub fn mul_sv(
a: f64, a: f64,
b: ForeignDataWrapper<Vector3>, b: ForeignDataWrapper<Vector3>,
) -> Result<ForeignDataWrapper<Vector3>, EvalError> { ) -> Result<ForeignDataWrapper<Vector3>, EvalError> {
Ok(ForeignDataWrapper::new(*b * a)) Ok(ForeignDataWrapper::new(*b * a))
} }
native_lisp_function_proxy!(fname = vmul, eval, dispatch = vmul_vs, dispatch = vmul_sv); #[native_lisp_function]
pub fn mul_ps(
a: ForeignDataWrapper<Point3>,
b: f64,
) -> Result<ForeignDataWrapper<Point3>, EvalError> {
Ok(ForeignDataWrapper::new(*a * b))
}
#[native_lisp_function]
pub fn mul_sp(
a: f64,
b: ForeignDataWrapper<Point3>,
) -> Result<ForeignDataWrapper<Point3>, EvalError> {
Ok(ForeignDataWrapper::new(*b * a))
}
native_lisp_function_proxy!(
fname = mul,
eval,
dispatch = mul_i,
dispatch = mul_f,
dispatch = mul_vs,
dispatch = mul_sv,
dispatch = mul_ps,
dispatch = mul_sp
);
#[native_lisp_function(eval)]
pub fn div_i(x: i64, y: i64) -> Result<f64, EvalError> {
Ok(x as f64 / y as f64)
}
#[native_lisp_function(eval)]
pub fn div_f(x: f64, y: f64) -> Result<f64, EvalError> {
Ok(x / y)
}
#[native_lisp_function]
pub fn div_vs(
a: ForeignDataWrapper<Vector3>,
b: f64,
) -> Result<ForeignDataWrapper<Vector3>, EvalError> {
Ok(ForeignDataWrapper::new(*a / b))
}
#[native_lisp_function]
pub fn div_sv(
a: f64,
b: ForeignDataWrapper<Vector3>,
) -> Result<ForeignDataWrapper<Vector3>, EvalError> {
Ok(ForeignDataWrapper::new(*b / a))
}
#[native_lisp_function]
pub fn div_ps(
a: ForeignDataWrapper<Point3>,
b: f64,
) -> Result<ForeignDataWrapper<Point3>, EvalError> {
Ok(ForeignDataWrapper::new(*a / b))
}
#[native_lisp_function]
pub fn div_sp(
a: f64,
b: ForeignDataWrapper<Point3>,
) -> Result<ForeignDataWrapper<Point3>, EvalError> {
Ok(ForeignDataWrapper::new(*b / a))
}
native_lisp_function_proxy!(
fname = div,
eval,
dispatch = div_i,
dispatch = div_f,
dispatch = div_vs,
dispatch = div_sv,
dispatch = div_ps,
dispatch = div_sp
);
#[native_lisp_function(eval)]
pub fn dot(
a: ForeignDataWrapper<Vector3>,
b: ForeignDataWrapper<Vector3>,
) -> Result<f64, EvalError> {
Ok(a.dot(&b))
}
#[native_lisp_function]
pub fn abs_i(a: i64) -> Result<i64, EvalError> {
Ok(a.abs())
}
#[native_lisp_function]
pub fn abs_f(a: f64) -> Result<f64, EvalError> {
Ok(a.abs())
}
#[native_lisp_function]
pub fn abs_v(a: ForeignDataWrapper<Vector3>) -> Result<f64, EvalError> {
Ok(a.dot(&a).sqrt())
}
native_lisp_function_proxy!(
fname = abs,
eval,
dispatch = abs_i,
dispatch = abs_f,
dispatch = abs_v
);
/// Adds the raytracing functions to the given environment layer. /// Adds the raytracing functions to the given environment layer.
pub fn mk_raytrace(layer: &mut EnvironmentLayer) { pub fn mk_raytrace(layer: &mut EnvironmentLayer) {
layer.set("point".to_string(), Expression::Function(point)); layer.set("point".to_string(), Expression::Function(point));
layer.set("point2".to_string(), Expression::Function(point2));
layer.set("vector".to_string(), Expression::Function(vector)); layer.set("vector".to_string(), Expression::Function(vector));
layer.set("color".to_string(), Expression::Function(color)); layer.set("color".to_string(), Expression::Function(color));
layer.set("light".to_string(), Expression::Function(light)); layer.set("light".to_string(), Expression::Function(light));
@@ -269,14 +547,37 @@ pub fn mk_raytrace(layer: &mut EnvironmentLayer) {
"checkerboard".to_string(), "checkerboard".to_string(),
Expression::Function(checkerboard), Expression::Function(checkerboard),
); );
layer.set(
"texture-plane".to_string(),
Expression::Function(texture_plane),
);
layer.set(
"mandelbrot-texture".to_string(),
Expression::Function(mandelbrot_texture),
);
layer.set("sphere".to_string(), Expression::Function(sphere)); layer.set("sphere".to_string(), Expression::Function(sphere));
layer.set(
"texture-sphere".to_string(),
Expression::Function(texture_sphere),
);
layer.set("scene".to_string(), Expression::Function(scene)); layer.set("scene".to_string(), Expression::Function(scene));
layer.set("scene-add".to_string(), Expression::Function(scene_add)); layer.set("scene-add".to_string(), Expression::Function(scene_add));
layer.set("camera".to_string(), Expression::Function(camera)); layer.set("camera".to_string(), Expression::Function(camera));
layer.set(
"camera-reposition".to_string(),
Expression::Function(camera_reposition),
);
layer.set("render".to_string(), Expression::Function(render)); layer.set("render".to_string(), Expression::Function(render));
layer.set(
"render-animation".to_string(),
Expression::Function(render_animation),
);
layer.set("sin".to_string(), Expression::Function(sin)); layer.set("sin".to_string(), Expression::Function(sin));
layer.set("cos".to_string(), Expression::Function(cos)); layer.set("cos".to_string(), Expression::Function(cos));
layer.set("vadd".to_string(), Expression::Function(vadd)); layer.set("+".to_string(), Expression::Function(add));
layer.set("vsub".to_string(), Expression::Function(vsub)); layer.set("-".to_string(), Expression::Function(sub));
layer.set("vmul".to_string(), Expression::Function(vmul)); layer.set("*".to_string(), Expression::Function(mul));
layer.set("/".to_string(), Expression::Function(div));
layer.set("dot".to_string(), Expression::Function(dot));
layer.set("abs".to_string(), Expression::Function(abs));
} }
+19
View File
@@ -3,5 +3,24 @@ pub mod lisp;
pub mod plane; pub mod plane;
pub mod scene; pub mod scene;
pub mod sphere; pub mod sphere;
mod texture;
pub mod types; pub mod types;
mod vec; mod vec;
#[derive(Debug, Clone)]
pub enum RTError {
EvalError(lispers_core::lisp::eval::EvalError),
FFMpegError(video_rs::Error),
}
impl From<lispers_core::lisp::eval::EvalError> for RTError {
fn from(value: lispers_core::lisp::eval::EvalError) -> Self {
RTError::EvalError(value)
}
}
impl From<video_rs::Error> for RTError {
fn from(value: video_rs::Error) -> Self {
RTError::FFMpegError(value)
}
}
+124 -11
View File
@@ -1,4 +1,7 @@
use super::types::{Intersect, Material, Point3, Scalar, Vector3}; use super::{
texture::TextureWrapper,
types::{Intersect, Material, Point3, Scalar, Vector3},
};
extern crate nalgebra as na; extern crate nalgebra as na;
@@ -26,6 +29,21 @@ pub struct Checkerboard {
projection_matrix: na::Matrix2x3<Scalar>, projection_matrix: na::Matrix2x3<Scalar>,
} }
/// Define a plane using a 2D texture function
#[derive(Clone)]
pub struct TexturePlane {
/// The position of the plane.
position: Point3,
/// The normal of the plane.
normal: Vector3,
/// The scale of the plane (factor for x,y passed to material function)
scale: f64,
/// A projection matrix to map 3D points to the 2D plane space.
projection_matrix: na::Matrix2x3<Scalar>,
/// The texture to use.
texture: TextureWrapper,
}
impl Plane { impl Plane {
/// Create a new plane. /// Create a new plane.
/// - `position` is the position of the plane. /// - `position` is the position of the plane.
@@ -66,6 +84,49 @@ impl Checkerboard {
} }
} }
impl TexturePlane {
/// Create a new Function Plane.
/// - `position` is the position of the plane.
/// - `normal` is the normal of the plane.
/// - `texture` the texture to use
/// - `scale` is the side-length of each square.
/// - `up` is "y" direction on the plane in 3D-Space.
pub fn new(
position: Point3,
normal: Vector3,
texture: TextureWrapper,
scale: f64,
up: Vector3,
) -> TexturePlane {
let right = up.cross(&normal).normalize();
TexturePlane {
position,
normal,
scale,
projection_matrix: na::Matrix3x2::from_columns(&[right, up]).transpose(),
texture,
}
}
}
fn plane_intersect(
position: Point3,
normal: Vector3,
ray: &super::types::Ray,
) -> Option<(Point3, Vector3, super::types::Scalar)> {
let denom = normal.dot(&ray.direction);
if denom != 0.0 {
let d = normal.dot(&position.coords);
let t = (d - normal.dot(&ray.origin.coords)) / denom;
if t > 1e-5 {
let point = ray.origin + ray.direction * t;
return Some((point, normal, t));
}
}
None
}
impl Intersect for Plane { impl Intersect for Plane {
fn intersect( fn intersect(
&self, &self,
@@ -76,19 +137,13 @@ impl Intersect for Plane {
super::types::Scalar, super::types::Scalar,
super::types::Material, super::types::Material,
)> { )> {
let denom = self.normal.dot(&ray.direction); if let Some((point, normal, t)) = plane_intersect(self.position, self.normal, ray) {
if denom != 0.0 { Some((point, normal, t, self.material.clone()))
let d = self.normal.dot(&self.position.coords); } else {
let t = (d - self.normal.dot(&ray.origin.coords)) / denom;
if t > 1e-5 {
let point = ray.origin + ray.direction * t;
return Some((point, self.normal, t, self.material.clone()));
}
}
None None
} }
} }
}
impl Intersect for Checkerboard { impl Intersect for Checkerboard {
fn intersect( fn intersect(
@@ -117,6 +172,29 @@ impl Intersect for Checkerboard {
} }
} }
impl Intersect for TexturePlane {
fn intersect(
&self,
ray: &super::types::Ray,
) -> Option<(
Point3,
Vector3,
super::types::Scalar,
super::types::Material,
)> {
if let Some((point, normal, t)) = plane_intersect(self.position, self.normal, ray) {
let v3 = point - self.position;
let v2 = self.projection_matrix * v3;
let material = self
.texture
.material_at(na::Point2::new(v2.x / self.scale, v2.y / self.scale));
Some((point, normal, t, material))
} else {
None
}
}
}
impl std::fmt::Display for Plane { impl std::fmt::Display for Plane {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!( write!(
@@ -137,6 +215,26 @@ impl std::fmt::Display for Checkerboard {
} }
} }
impl std::fmt::Debug for TexturePlane {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"(function-plane position: {:?}, normal: {:?}, scale: {:?})",
self.position, self.normal, self.scale,
)
}
}
impl std::fmt::Display for TexturePlane {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"(function-plane position: {}, normal: {}, scale: {})",
self.position, self.normal, self.scale,
)
}
}
impl PartialOrd for Plane { impl PartialOrd for Plane {
fn partial_cmp(&self, _other: &Self) -> Option<std::cmp::Ordering> { fn partial_cmp(&self, _other: &Self) -> Option<std::cmp::Ordering> {
None None
@@ -148,3 +246,18 @@ impl PartialOrd for Checkerboard {
None None
} }
} }
impl PartialEq for TexturePlane {
fn eq(&self, other: &Self) -> bool {
self.normal == other.normal
&& self.position == other.position
&& self.projection_matrix == other.projection_matrix
&& self.scale == other.scale
}
}
impl PartialOrd for TexturePlane {
fn partial_cmp(&self, _other: &Self) -> Option<std::cmp::Ordering> {
None
}
}
+74 -11
View File
@@ -1,4 +1,7 @@
use super::types::{Intersect, Material, Point3, Ray, Scalar, Vector3}; use super::{
texture::TextureWrapper,
types::{Intersect, Material, Point2, Point3, Ray, Scalar, Vector3},
};
extern crate nalgebra as na; extern crate nalgebra as na;
@@ -13,6 +16,17 @@ pub struct Sphere {
material: Material, material: Material,
} }
/// A sphere in 3D space
#[derive(PartialEq, Clone, Debug)]
pub struct TextureSphere {
/// Center of the sphere
center: Point3,
/// Radius of the sphere
radius: Scalar,
/// texture of the sphere
texture: TextureWrapper,
}
impl Sphere { impl Sphere {
/// Create a new sphere at `center` with `radius` and `material`. /// Create a new sphere at `center` with `radius` and `material`.
pub fn new(center: Point3, radius: Scalar, material: Material) -> Sphere { pub fn new(center: Point3, radius: Scalar, material: Material) -> Sphere {
@@ -27,13 +41,12 @@ impl Sphere {
/// Numerical error tolerance /// Numerical error tolerance
const EPSILON: Scalar = 1e-5; const EPSILON: Scalar = 1e-5;
impl Intersect for Sphere { fn intersect(ray: &Ray, center: &Point3, radius: Scalar) -> Option<(Point3, Vector3, Scalar)> {
fn intersect(&self, ray: &Ray) -> Option<(Point3, Vector3, Scalar, Material)> { let co = ray.origin - center;
let co = ray.origin - self.center;
let a = ray.direction.dot(&ray.direction); let a = ray.direction.dot(&ray.direction);
let b = 2.0 * ray.direction.dot(&co); let b = 2.0 * ray.direction.dot(&co);
let c = co.dot(&co) - (self.radius * self.radius); let c = co.dot(&co) - (radius * radius);
let d = b * b - 4.0 * a * c; let d = b * b - 4.0 * a * c;
if d >= 0.0 { if d >= 0.0 {
@@ -52,17 +65,24 @@ impl Intersect for Sphere {
if t < Scalar::MAX { if t < Scalar::MAX {
let isect_pt: Point3 = ray.origin + ray.direction * t; let isect_pt: Point3 = ray.origin + ray.direction * t;
return Some(( if c >= 0.0 {
isect_pt, return Some((isect_pt, (isect_pt - center) / radius, t));
(isect_pt - self.center) / self.radius, } else {
t, return Some((isect_pt, -(isect_pt - center) / radius, t));
self.material.clone(), }
));
} }
} }
None None
} }
impl Intersect for Sphere {
fn intersect(&self, ray: &Ray) -> Option<(Point3, Vector3, Scalar, Material)> {
match intersect(ray, &self.center, self.radius) {
Some((isect_pt, normal, t)) => Some((isect_pt, normal, t, self.material.clone())),
None => None,
}
}
} }
impl std::fmt::Display for Sphere { impl std::fmt::Display for Sphere {
@@ -80,3 +100,46 @@ impl PartialOrd for Sphere {
None None
} }
} }
impl TextureSphere {
/// Create a new sphere at `center` with `radius` and `texture`.
pub fn new(center: Point3, radius: Scalar, texture: TextureWrapper) -> TextureSphere {
TextureSphere {
center,
radius,
texture,
}
}
}
impl Intersect for TextureSphere {
fn intersect(&self, ray: &Ray) -> Option<(Point3, Vector3, Scalar, Material)> {
match intersect(ray, &self.center, self.radius) {
Some((isect_pt, normal, t)) => {
let n_isect_pt = (isect_pt - self.center) / self.radius;
let uv: Point2 = Point2::new(
0.5 + (n_isect_pt.z.atan2(n_isect_pt.x) / (2.0 * std::f64::consts::PI)),
0.5 - (n_isect_pt.y).asin() / std::f64::consts::PI,
);
Some((isect_pt, normal, t, self.texture.material_at(uv)))
}
None => None,
}
}
}
impl std::fmt::Display for TextureSphere {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"(sphere center: {}, radius: {}, texture: {})",
self.center, self.radius, self.texture
)
}
}
impl PartialOrd for TextureSphere {
fn partial_cmp(&self, _other: &Self) -> Option<std::cmp::Ordering> {
None
}
}
+127
View File
@@ -0,0 +1,127 @@
use std::fmt::Debug;
use std::fmt::Display;
use std::sync::Arc;
use as_any::AsAny;
use nalgebra as na;
use super::types::Color;
use super::types::Material;
use super::types::Point2;
use super::types::Scalar;
pub trait Texture: Display + Debug + AsAny + Sync + Send {
fn material_at(&self, pt: Point2) -> Material;
}
#[derive(Clone)]
pub struct TextureWrapper(Arc<dyn Texture>);
impl TextureWrapper {
pub fn new<T: Texture>(texture: T) -> Self {
Self(Arc::new(texture))
}
}
impl Display for TextureWrapper {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Display::fmt(&self.0, f)
}
}
impl Debug for TextureWrapper {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Debug::fmt(&self.0, f)
}
}
impl TextureWrapper {
pub fn material_at(&self, pt: Point2) -> Material {
self.0.material_at(pt)
}
}
impl PartialEq for TextureWrapper {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.0, &other.0)
}
}
impl PartialOrd for TextureWrapper {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
PartialOrd::partial_cmp(&Arc::as_ptr(&self.0).addr(), &Arc::as_ptr(&other.0).addr())
}
}
pub struct MandelbrotTexture {
scale: Scalar,
at: Point2,
max_iter: u32,
ambient_color: Color,
diffuse_color: Color,
specular_color: Color,
}
impl MandelbrotTexture {
pub fn new(
scale: Scalar,
at: Point2,
max_iter: u32,
ambient_color: Color,
diffuse_color: Color,
specular_color: Color,
) -> Self {
Self {
scale,
at,
max_iter,
ambient_color,
diffuse_color,
specular_color,
}
}
}
impl Texture for MandelbrotTexture {
fn material_at(&self, pt: Point2) -> Material {
let x = (pt.x / self.scale) + self.at.x;
let y = (pt.y / self.scale) + self.at.y;
let mut z = na::Vector2::new(0.0, 0.0);
let mut n = 0;
while z.norm() < 2.0 && n < self.max_iter {
let xtemp = z.x * z.x - z.y * z.y + x;
z.y = 2.0 * z.x * z.y + y;
z.x = xtemp;
n += 1;
}
let c = n as f64 / self.max_iter as f64;
Material {
ambient_color: self.ambient_color * c,
diffuse_color: self.diffuse_color * c,
specular_color: self.specular_color * c,
shininess: (1.0 - c) * 10.0,
mirror: 1.0 - c,
}
}
}
impl Display for MandelbrotTexture {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"MandelbrotTexture{{at={}, max_iter={}}}",
self.at, self.max_iter
)
}
}
impl Debug for MandelbrotTexture {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"MandelbrotTexture{{at={:?}, max_iter={:?}}}",
self.at, self.max_iter
)
}
}
+2
View File
@@ -10,6 +10,8 @@ pub type Scalar = f64;
pub type Vector3 = na::Vector3<Scalar>; pub type Vector3 = na::Vector3<Scalar>;
/// The Point3 type to use for raytracing /// The Point3 type to use for raytracing
pub type Point3 = na::Point3<Scalar>; pub type Point3 = na::Point3<Scalar>;
/// The Point2 type to use for texture lookups
pub type Point2 = na::Point2<Scalar>;
/// The Color type to use for raytracing /// The Color type to use for raytracing
pub type Color = Vector3; pub type Color = Vector3;