Merge pull request #432 from elrnv/swizzle

Swizzle Operators
This commit is contained in:
Brendan Zabarauskas 2017-10-02 09:13:07 +11:00 committed by GitHub
commit ccc81b4760
7 changed files with 200 additions and 0 deletions

View file

@ -18,6 +18,7 @@ name = "cgmath"
[features] [features]
unstable = [] unstable = []
swizzle = []
[dependencies] [dependencies]
approx = "0.1" approx = "0.1"

View file

@ -32,6 +32,32 @@ vectors"), meaning when transforming a vector with a matrix, the matrix goes
on the left. This is reflected in the fact that cgmath implements the on the left. This is reflected in the fact that cgmath implements the
multiplication operator for Matrix * Vector, but not Vector * Matrix. multiplication operator for Matrix * Vector, but not Vector * Matrix.
## Features
### Swizzling
This library offers an optional feature called
["swizzling"](https://en.wikipedia.org/wiki/Swizzling_(computer_graphics))
widely familiar to GPU programmers. To enable swizzle operators, pass the
`--features="swizzle"` option to cargo. Enabling this feature will increase
the size of the cgmath library by approximately 0.6MB. This isn't an
issue if the library is linked in the "normal" way by adding cgmath as a
dependency in Cargo.toml, which will link cgmath statically so all unused
swizzle operators will be optimized away by the compiler in release mode.
#### Example
If we have
```rust
let v = Vector3::new(1.0, 2.0, 3.0);
```
then `v.xyxz()` produces a
```rust
Vector4 { x: 1.0, y: 2.0, z: 1.0, w: 3.0 }
```
and `v.zy()` produces a
```rust
Vector2 { x: 3.0, y: 2.0 }
```
## Limitations ## Limitations
cgmath is _not_ an n-dimensional library and is aimed at computer graphics cgmath is _not_ an n-dimensional library and is aimed at computer graphics

96
build.rs Normal file
View file

@ -0,0 +1,96 @@
use std::fs::File;
use std::io::Write;
use std::path::Path;
use std::env;
use std::string::String;
/// Generate the name of the swizzle function and what it returns.
/// NOTE: This function assumes that variables are in ASCII format
#[cfg(feature = "swizzle")]
fn gen_swizzle_nth<'a>(variables: &'a str, mut i: usize, upto: usize) -> Option<(String, String)> {
debug_assert!(i > 0); // zeroth permutation is empty
let mut swizzle_impl = String::new();
let mut swizzle = String::new();
let n = variables.len()+1;
for _ in 0..upto {
if i == 0 { break; }
if i % n == 0 { return None; }
let c = variables.as_bytes()[i%n - 1] as char;
swizzle.push(c);
swizzle_impl.push_str(&format!("self.{}, ", c));
i = i/n;
}
Some((swizzle, swizzle_impl))
}
/// A function that generates swizzle functions as a string.
/// `variables`: swizzle variables (e.g. "xyz")
/// `upto`: largest output vector size (e.g. for `variables = "xy"` and `upto = 4`, `xyxy()` is a
/// valid swizzle operator.
/// NOTE: This function assumes that variables are in ASCII format
#[cfg(feature = "swizzle")]
fn gen_swizzle_functions(variables: &'static str, upto: usize) -> String {
let mut result = String::new();
let nn = (variables.len()+1).pow(upto as u32);
for i in 1..nn {
if let Some((swizzle_name, swizzle_impl)) = gen_swizzle_nth(variables, i, upto) {
let dim = format!("{}", swizzle_name.len());
result.push_str(
&format!("
/// Swizzle operator that creates a new type with dimension {2} from variables `{0}`.
#[inline] pub fn {0}(&self) -> $vector_type{2}<$S> {{ $vector_type{2}::new({1}) }}\n",
swizzle_name, swizzle_impl, dim));
}
}
result
}
#[cfg(not(feature = "swizzle"))]
fn gen_swizzle_functions(_: &'static str, _: usize) -> String {
String::new()
}
/// This script generates the macro for building swizzle operators for multidimensional
/// vectors and points. This macro is included in macros.rs
fn main() {
// save the file to output directory
let out_dir = env::var("OUT_DIR").unwrap();
let swizzle_file_path = Path::new(&out_dir).join("swizzle_operator_macro.rs");
// This is the string representing the generated macro
let data = format!(
"/// Generate glm/glsl style swizzle operators
macro_rules! impl_swizzle_functions {{
($vector_type1:ident, $vector_type2:ident, $vector_type3:ident, $S:ident, x) => {{
{x3}
}};
($vector_type1:ident, $vector_type2:ident, $vector_type3:ident, $S:ident, xy) => {{
{xy3}
}};
($vector_type1:ident, $vector_type2:ident, $vector_type3:ident, $S:ident, xyz) => {{
{xyz3}
}};
($vector_type1:ident, $vector_type2:ident, $vector_type3:ident, $vector_type4:ident, $S:ident, x) => {{
{x4}
}};
($vector_type1:ident, $vector_type2:ident, $vector_type3:ident, $vector_type4:ident, $S:ident, xy) => {{
{xy4}
}};
($vector_type1:ident, $vector_type2:ident, $vector_type3:ident, $vector_type4:ident, $S:ident, xyz) => {{
{xyz4}
}};
($vector_type1:ident, $vector_type2:ident, $vector_type3:ident, $vector_type4:ident, $S:ident, xyzw) => {{
{xyzw4}
}};
}}", x3 = gen_swizzle_functions("x", 3),
xy3 = gen_swizzle_functions("xy", 3),
xyz3 = gen_swizzle_functions("xyz", 3),
x4 = gen_swizzle_functions("x", 4),
xy4 = gen_swizzle_functions("xy", 4),
xyz4 = gen_swizzle_functions("xyz", 4),
xyzw4 = gen_swizzle_functions("xyzw", 4));
let mut f = File::create(swizzle_file_path)
.expect("Unable to create file that defines the swizzle operator macro.");
f.write_all(data.as_bytes()).expect("Unable to write swizzle operator macro.");
}

View file

@ -476,3 +476,5 @@ macro_rules! impl_mint_conversions {
} }
} }
} }
include!(concat!(env!("OUT_DIR"), "/swizzle_operator_macro.rs"));

View file

@ -69,6 +69,8 @@ impl<S: BaseNum> Point1<S> {
pub fn new(x: S) -> Point1<S> { pub fn new(x: S) -> Point1<S> {
Point1 { x: x } Point1 { x: x }
} }
impl_swizzle_functions!(Point1, Point2, Point3, S, x);
} }
impl<S: BaseNum> Point2<S> { impl<S: BaseNum> Point2<S> {
@ -76,6 +78,8 @@ impl<S: BaseNum> Point2<S> {
pub fn new(x: S, y: S) -> Point2<S> { pub fn new(x: S, y: S) -> Point2<S> {
Point2 { x: x, y: y } Point2 { x: x, y: y }
} }
impl_swizzle_functions!(Point1, Point2, Point3, S, xy);
} }
impl<S: BaseNum> Point3<S> { impl<S: BaseNum> Point3<S> {
@ -83,6 +87,8 @@ impl<S: BaseNum> Point3<S> {
pub fn new(x: S, y: S, z: S) -> Point3<S> { pub fn new(x: S, y: S, z: S) -> Point3<S> {
Point3 { x: x, y: y, z: z } Point3 { x: x, y: y, z: z }
} }
impl_swizzle_functions!(Point1, Point2, Point3, S, xyz);
} }
impl<S: BaseNum> Point3<S> { impl<S: BaseNum> Point3<S> {

View file

@ -605,6 +605,8 @@ impl<S: BaseNum> Vector1<S> {
pub fn unit_x() -> Vector1<S> { pub fn unit_x() -> Vector1<S> {
Vector1::new(S::one()) Vector1::new(S::one())
} }
impl_swizzle_functions!(Vector1, Vector2, Vector3, Vector4, S, x);
} }
impl<S: BaseNum> Vector2<S> { impl<S: BaseNum> Vector2<S> {
@ -632,6 +634,8 @@ impl<S: BaseNum> Vector2<S> {
pub fn extend(self, z: S) -> Vector3<S> { pub fn extend(self, z: S) -> Vector3<S> {
Vector3::new(self.x, self.y, z) Vector3::new(self.x, self.y, z)
} }
impl_swizzle_functions!(Vector1, Vector2, Vector3, Vector4, S, xy);
} }
impl<S: BaseNum> Vector3<S> { impl<S: BaseNum> Vector3<S> {
@ -674,6 +678,8 @@ impl<S: BaseNum> Vector3<S> {
pub fn truncate(self) -> Vector2<S> { pub fn truncate(self) -> Vector2<S> {
Vector2::new(self.x, self.y) Vector2::new(self.x, self.y)
} }
impl_swizzle_functions!(Vector1, Vector2, Vector3, Vector4, S, xyz);
} }
impl<S: BaseNum> Vector4<S> { impl<S: BaseNum> Vector4<S> {
@ -718,6 +724,8 @@ impl<S: BaseNum> Vector4<S> {
_ => panic!("{:?} is out of range", n), _ => panic!("{:?} is out of range", n),
} }
} }
impl_swizzle_functions!(Vector1, Vector2, Vector3, Vector4, S, xyzw);
} }
/// Dot product of two vectors. /// Dot product of two vectors.

61
tests/swizzle.rs Normal file
View file

@ -0,0 +1,61 @@
// Copyright 2013-2017 The CGMath Developers. For a full listing of the authors,
// refer to the Cargo.toml file at the top-level directory of this distribution.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#![cfg(feature = "swizzle")]
extern crate cgmath;
use cgmath::{Point1, Point2, Point3, Vector1, Vector2, Vector3, Vector4};
// Sanity checks
#[test]
fn test_point_swizzle() {
let p1 = Point1::new(1.0);
let p2 = Point2::new(1.0, 2.0);
let p3 = Point3::new(1.0, 2.0, 3.0);
assert_eq!(p1.x(), p1);
assert_eq!(p2.x(), p1);
assert_eq!(p2.y(), Point1::new(2.0));
assert_eq!(p2.xx(), Point2::new(1.0, 1.0));
assert_eq!(p2.xy(), p2);
assert_eq!(p2.yx(), Point2::new(2.0, 1.0));
assert_eq!(p2.yy(), Point2::new(2.0, 2.0));
assert_eq!(p3.x(), p1);
assert_eq!(p3.y(), Point1::new(2.0));
assert_eq!(p3.xy(), p2);
assert_eq!(p3.zy(), Point2::new(3.0, 2.0));
assert_eq!(p3.yyx(), Point3::new(2.0, 2.0, 1.0));
}
#[test]
fn test_vector_swizzle() {
let p1 = Vector1::new(1.0);
let p2 = Vector2::new(1.0, 2.0);
let p3 = Vector3::new(1.0, 2.0, 3.0);
let p4 = Vector4::new(1.0, 2.0, 3.0, 4.0);
assert_eq!(p1.x(), p1);
assert_eq!(p2.x(), p1);
assert_eq!(p2.y(), Vector1::new(2.0));
assert_eq!(p2.xx(), Vector2::new(1.0, 1.0));
assert_eq!(p2.xy(), p2);
assert_eq!(p2.yx(), Vector2::new(2.0, 1.0));
assert_eq!(p2.yy(), Vector2::new(2.0, 2.0));
assert_eq!(p3.x(), p1);
assert_eq!(p3.y(), Vector1::new(2.0));
assert_eq!(p3.xy(), p2);
assert_eq!(p3.zy(), Vector2::new(3.0, 2.0));
assert_eq!(p3.yyx(), Vector3::new(2.0, 2.0, 1.0));
assert_eq!(p4.xyxy(), Vector4::new(1.0, 2.0, 1.0, 2.0));
}