草庐IT

CesiumJS PrimitiveAPI 高级着色入门 - 从参数化几何与 Fabric 材质到着色器 - 上篇

岭南灯火 2023-03-28 原文


Primitive API 还包括 Appearance APIGeometry API 两个主要部分,是 CesiumJS 挡在原生 WebGL 接口之前的最底层图形封装接口(公开的),不公开的最底层接口是 DrawCommand 为主的 Renderer API,DC 对实时渲染管线的技术要求略高,可定制性也高,这篇还是以 Primitive API 为侧重点。

0. 基础

0.1. 坐标系基础

这里的“坐标系”特指 WebGL 图形渲染的坐标系。Primitive API 收到的几何数据,默认没有任何坐标系(即最基本的空间直角坐标),想要移动到地表感兴趣的地方,需要借助 ENU 转换矩阵,或者把几何顶点的坐标直接设为 EPSG:4978 坐标(即所谓通俗的“世界坐标”)。

ENU 转换矩阵,用道家八卦的说法类似“定中宫”。它能将坐标转换到这样一个 ENU 地表局部坐标系上:

  • 指定一处地表点(经纬度)为坐标原点

  • 以贴地正东方(ENU 中的 E)为正 X 轴

  • 以贴地正北方(ENU 中的 N)为正 Y 轴

  • 以地心到坐标原点的方向(即 ENU 中的 U,up)为正 Z 轴

这样一个 ENU 坐标系上的局部坐标左乘 ENU 转换矩阵后,就能得到标准的 EPSG:4978 世界坐标。

GIS 中的投影坐标、经纬坐标不太适用,需要转换。

0.2. 合并批次

虽然 WebGL 支持实例绘制技术,但是 Primitive API 减少绘制调用并不是通过这个思路来的,而是尽可能地把 Vertex 数据合并,这个叫做 Batch,也就是“合并批次(并批)”。

在 CesiumJS 的 API 文档中能看到 new Primitive() 时,可以传递一个 GeometryInstance 或者 GeometryInstance 数组,而 GeometryInstance 对象又能复用具体的某个Geometry 对象,仅在几何的变换位置(通过矩阵表达)、顶点属性(Vertex Attribute)上做差异化。

CesiumJS 会在 WebWorker 中异步地拼装这些几何数据,尽可能一次性发送给底层的 Renderer,以达到尽可能少的 DC。

我没有十分精确地去确认这个并批的概念和 CesiumJS 源码中合并的过程,如有错误请指出。

1. 参数化几何

这是公开 API 的最常规用法了,你可以在官方指引文档中学习如何使用参数化几何来创建内置的几何对象:Custom Geometry and Appearance

1.1. 几何类清单

CesiumJS 内置的参数几何有如下数种:

  • 立方体(盒) - BoxGeometry & BoxOutlineGeometry

  • 矩形 - RectangleGeometry & RectangleOutlineGeometry

  • 圆形 - CircleGeometry & CircleOutlineGeometry

  • 线的缓冲区(可设定转角类型和挤出高度) - CorridorGeometry & CorridorOutlineGeometry

  • 圆柱、圆台、圆锥 - CylinderGeometry & CylinderOutlineGeometry

  • 椭圆、椭圆柱 - EllipseGeometry & EllipseOutlineGeometry

  • 椭球面 - EllipsoidGeometry & EllipsoidOutlineGeometry

  • 多边形(可挤出高度) - PolygonGeometry & PolygonOutlineGeometry

  • 多段线 - PolylineGeometry & SimplePolylineGeometry

  • 多段线等径柱体 - PolylineVolumeGeometry & PolylineVolumeOutlineGeometry

  • 球面 - SphereGeometry & SphereOutlineGeometry

  • 墙体 - WallGeometry & WallOutlineGeometry

  • 四棱台(视锥截头体) - FrustumGeometry & FrustumOutlineGeometry

  • 平面 - PlaneGeometry & PlaneOutlineGeometry

  • 共面多边形 - CoplanarPolygonGeometry & CoplanarPolygonOutlineGeometry

  • Esri I3S 专用的几何 - I3SGeometry

这里有两个特别说明:

  • 除了 I3SGeometry 比较特殊外,其它的几何对象都有其对应的边线几何对象(边线不是三角网格)

  • CoplanarPolygonGeometryPolygonGeometry 两个 API 很像,但是前者是 2018 年 1.48 后来添加的 API,适用于顶点共面的多边形;不共面的顶点在 PolygonGeometry 中可能会引起崩溃,但在这个共面多边形 API 不会(尽管可能会产生一些不可预测的三角形)。在 PolygonGeometry 出现三角形显示不正常、不完整的情况,可考虑用这个共面多边形 API;也支持挖洞。

可见 CesiumJS 对参数几何的支持是比较丰富的。

1.2. 举例

以下即两个椭球体的实例绘制示例代码:

import {
  EllipsoidGeometry,
  GeometryInstance,
  Matrix4,
  Cartesian3,
  Transforms,
  PerInstanceColorAppearance,
  Color,
  ColorGeometryInstanceAttribute,
  Primitive,
} from 'cesium'


// 只创建一个椭球体几何对象,下面会复用
const ellipsoidGeometry = new EllipsoidGeometry({
  vertexFormat: PerInstanceColorAppearance.VERTEX_FORMAT,
  radii: new Cartesian3(300000.0, 200000.0, 150000.0),
})

// 亮蓝色椭球体绘制实例
const cyanEllipsoidInstance = new GeometryInstance({
  geometry: ellipsoidGeometry,
  modelMatrix: Matrix4.multiplyByTranslation(
    Transforms.eastNorthUpToFixedFrame(
      Cartesian3.fromDegrees(-100.0, 40.0)
    ),
    new Cartesian3(0.0, 0.0, 150000.0),
    new Matrix4()
  ),
  attributes: {
    color: ColorGeometryInstanceAttribute.fromColor(Color.CYAN),
  },
})

// 橙色椭球体绘制实例
const orangeEllipsoidInstance = new GeometryInstance({
  geometry: ellipsoidGeometry,
  modelMatrix: Matrix4.multiplyByTranslation(
    Transforms.eastNorthUpToFixedFrame(
      Cartesian3.fromDegrees(-100.0, 40.0)
    ),
    new Cartesian3(0.0, 0.0, 450000.0),
    new Matrix4()
  ),
  attributes: {
    color: ColorGeometryInstanceAttribute.fromColor(Color.ORANGE),
  },
})

scene.primitives.add(
  new Primitive({
    geometryInstances: [cyanEllipsoidInstance, orangeEllipsoidInstance],
    appearance: new PerInstanceColorAppearance({
      translucent: false,
      closed: true,
    }),
  })
)

代码就不详细解释了,需要有一定的 WebGL 基础,否则对 vertexFormatattributes 等字段会有些陌生。

如下图所示:

1.3. 纯手搓几何

CesiumJS 的封装能力和 API 设计能力可谓一绝,它给开发者留下了非常多层级的调用方法。除了 1.1、1.2 提到的内置几何体,假如你对 WebGL 的数据格式(VertexBuffer)能熟练应用的话,你可以使用 Geometry + GeometryAttribute 类自己创建几何体对象,查阅 Geometry 的文档,它提供了一个很简单的例子:

import { Geometry, GeometryAttribute, ComponentDatatype, PrimitiveType, BoundingSphere } from 'cesium'

const positions = new Float64Array([
  0.0, 0.0, 0.0,
  7500000.0, 0.0, 0.0,
  0.0, 7500000.0, 0.0
])

const geometry = new Geometry({
  attributes: {
    position: new GeometryAttribute({
      componentDatatype: ComponentDatatype.DOUBLE,
      componentsPerAttribute: 3,
      values: positions
    })
  },
  indices: new Uint16Array([0, 1, 1, 2, 2, 0]),
  primitiveType: PrimitiveType.LINES,
  boundingSphere: BoundingSphere.fromVertices(positions)
})

然后就可以继续创建 GeometryInstance,搭配外观、材质对象创建 Primitive 了。

这一个属于高阶用法,适用于有自定义二进制 3D 数据格式能力的读者。

这一步还没有触及 CesiumJS 的最底层,挡在 WebGL 之前的是一层非公开的 API,叫 DrawCommand,有兴趣可以自己研究。

1.4. *子线程异步生成几何

有部分参数化几何对象经过一系列逻辑运送后,是要在 WebWorker 内三角化、生成顶点缓冲的。

这小节内容比较接近源码解析,不会讲太详细。从 Primitive.prototype.update 方法中模块内函数 loadAsynchronous 看起:

Primitive.prototype.update = function (frameState) {
  /* ... */
  if (
    this._state !== PrimitiveState.COMPLETE &&
    this._state !== PrimitiveState.COMBINED
  ) {
    if (this.asynchronous) {
      loadAsynchronous(this, frameState);
    } else { /* ... */ }
  }
  /* ... */
}

在这个 loadAsynchronous 函数内,会调度一些 TaskProcessor 对象,这些 TaskProcessor 会通过 WebWorker 的消息传递来完成 Geometry 的 Vertex 创建。这个过程很复杂,就不展开了。

如果你感兴趣,打开浏览器的开发者工具,在 “源代码” 选项卡左侧的“页面”中,能看到一堆 “cesiumWorkerBootstrapper” 在运行。每一个,背后都是一个内嵌的 requirejs 在调度额外的异步模块,这些异步模块在默默地为主页面生成数据。

2. 使用材质

这一节讲 Primitive API 配套的第二个大类,Appearance + Material API,也叫外观材质 API,它允许开发者为自己的 Primitive 编写着色器。

2.1. 外观 API

CesiumJS 提供了如下几个具体的 Appearance 类:

  • MaterialAppearance - 材质外观,通用型,适用于第 1 节中大部分 Geometry

  • EllipsoidSurfaceAppearance - 上一个的子类,允许用在椭球面上的一些几何,例如 Polygon、Rectangle 等几何类型,这个外观类使用算法来表达部分顶点属性以节约数据大小

  • PerInstanceColorAppearance - 如果每个 GeometryInstance 用的是单独的颜色,可以用这个外观类,在 1.2 的例子中就用到这个类

  • PolylineMaterialAppearance - 使用材质(下一小节)来给有宽度的折线着色

  • PolylineColorAppearance - 使用逐顶点或逐线段来给有宽度的折线着色

外观类有一个抽象父类 Appearance(JavaScript 中没有抽象类,CesiumJS 也没有继承,大致意思,理解记可),上述 5 个均为它的实现类。

通常,为 Primitive 几何着色的主要职责在材质类,但是即使没有材质类,完全通过 GLSL 代码,设定外观类的顶点着色器和片元着色器(当然,要合规)也是可以完成渲染的。

下面就演示一下用 MaterialAppearance 与着色器代码实现立方体几何对象(BoxGeometry)的着色案例:

import {
  MaterialAppearance,
  Material,
  BoxGeometry,
  Matrix4,
  Cartesian3,
  Transforms,
  GeometryInstance,
  Primitive,
  VertexFormat,
} from 'cesium'

const scene = viewer.scene

// 创建 ENU 转换矩阵后,再基于 ENU 转换矩阵作 Z 轴平移 500000 * 0.5 个单位
const boxModelMatrix = Matrix4.multiplyByTranslation(
  Transforms.eastNorthUpToFixedFrame(Cartesian3.fromDegrees(112.0, 23.0)),
  new Cartesian3(0.0, 0.0, 500000 * 0.5),
  new Matrix4()
)
// 创建 Geometry 和 Instance
const boxGeometry = BoxGeometry.fromDimensions({
  vertexFormat: VertexFormat.POSITION_NORMAL_AND_ST, // 注意这里,下面要细说
  dimensions: new Cartesian3(400000.0, 300000.0, 500000.0),
})
const boxGeometryInstance = new GeometryInstance({
  geometry: boxGeometry,
  modelMatrix: boxModelMatrix, // 应用 ENU + 平移矩阵
})

// 准备 fabric shader 材质和外观对象
const shader = `czm_material czm_getMaterial(czm_materialInput materialInput) {
  czm_material material = czm_getDefaultMaterial(materialInput);
  material.diffuse = vec3(0.8, 0.2, 0.1);
  material.specular = 3.0;
  material.shininess = 0.8;
  material.alpha = 0.6;
  return material;
}`
const appearance = new MaterialAppearance({
  material: new Material({
    fabric: {
      source: shader
    }
  }),
})

scene.primitives.add(
  new Primitive({
    geometryInstances: boxGeometryInstance,
    appearance: appearance,
  })
)

然后你就能获得一个 blingbling 的立方块:

注意我在创建 BoxGeometry 时,留了一行注释:

vertexFormat: VertexFormat.POSITION_NORMAL_AND_ST,

使用 WebGL 原生接口的朋友应该知道这个,这个 VertexFormat 是指定要为参数几何体生成什么 顶点属性(VertexAttribute)。这里指定的是 POSITION_NORMAL_AND_ST,即生成的 VertexBuffer 中会包含顶点的坐标、法线、纹理坐标三个顶点属性。CesiumJS 的教程资料上说过,这个顶点格式参数,几何和外观对象要一一匹配才能兼容。

默认的,所有的 Geometry 对象都不需要传递这个,默认都是 VertexFormat.DEFAULT,也即 VertexFormat.POSITION_NORMAL_AND_ST。不妨设置成这个 POSITION_AND_NORMAL

vertexFormat: VertexFormat.POSITION_AND_NORMAL,

虽然法线影响光照,但是这里只是缺少了纹理坐标,盒子就没有 blingbling 的效果了:

具体的着色逻辑不深究,但是足够说明问题:这个 vertexFormat 会影响几何体的着色效果。

还有一个与外观有关的参数,那就是 new Primitive 时的构造参数 compressVertices,这个值默认是 true,即会根据几何体的 vertexFormat 参数来决定是否压缩 VertexBuffer。

如果设为:

// ...
const boxGeometry = BoxGeometry.fromDimensions({
  vertexFormat: VertexFormat.POSITION_AND_NORMAL,
  dimensions: new Cartesian3(400000.0, 300000.0, 500000.0),
})

// ...

new Primitive({
  geometryInstances: boxGeometryInstance,
  appearance: appearance,
  compressVertices: false
})

即不压缩顶点缓冲,但是 vertexFormat 设置的格式缺少了其中某一个,比如这里就缺少了纹理坐标,那么就会出现顶点缓冲和顶点格式不匹配的情况,会出现报错:

通常,使用 MaterialAppearance 能搭配大多数几何类了,也可以自己使用 Geometry + GeometryAttribute 这两个最基础的类创建出自定义的 Geometry,搭配使用。

只有极少数的情况,需要去动外观对象的两个着色器,这里先不展开,高阶用法会在第 3 节讲解。

2.2. 材质 API

CesiumJS 有自己的材质规则,叫做 Fabric 材质,全文参考文档 Fabric,在 2.3、2.4 小节会展开。

先看看直接实例化的参数。使用 new Material({}) 创建一个材质对象,除了 fabric 参数外,还需要这几个参数(有些是可选的):

  • strict: boolean,默认 false,即是否严格检查材质与 uniform、嵌套材质的匹配问题

  • translucent: boolean | (m: Material) => boolean,默认 true,为真则使用此材质的几何体允许有半透明

  • minificationFilter: TextureMinificationFilter,默认 TextureMinificationFilter.LINEAR,采样参数

  • magnificationFilter: TextureMagnificationFilter,默认 TextureMagnificationFilter.LINEAR,采样参数

fabric 参数,则是 Fabric 材质的全部内容,如果不使用内置材质类型要自己写材质的话,就需要认真研究这个 fabric 对象的参数规则了。

2.3. Fabric 材质初步 - 内置材质、材质缓存与 uniform

如几何、外观 API 一样,Material 类也给予了开发者一定的内置材质,略像简单工厂模式。只需要使用 Material.fromType() 就可以使用内置的十几种写好着色器的材质。

内置材质也是通过正经的 Fabric 对象创建的,有兴趣的可以看源码,所以内置材质也归为 Fabric 内容

列举几种基础材质和几种常见材质:

  • 常见材质 Material.fromType('Color') - 纯颜色

  • 常见材质 Material.fromType('Image') - 普通贴图

  • 基础材质 Material.fromType('DiffuseMap') - 漫反射贴图

  • 基础材质 Material.fromType('NormalMap') - 法线贴图

  • 基础材质 Material.fromType('SpecularMap') - 高光贴图

  • ...

具体的可以查看 Material 类的 API 文档,文档页面的最顶部就列举了若干种 type 对应的内置材质。fromType() 方法还可以传递第二个参数,第二个参数是这个材质所需要的 uniforms,会应用到着色器对应的 uniform 变量上。

例如,文档中对透明度贴图的 uniform 描述是这样的:

你就可以通过传递这些 uniform 值,来决定着色器使用传入的 image 的哪个 channel,以及要 repeat 的程度:

const alphaMapMaterial = Material.fromType('AlphaMap', {
  image: '相对于网页运行时的图片路径;网络地址绝对路径;base64图片', // 对多种图片地址有兼容
  channel: 'a', // 使用图片的 alpha 通道,根据图片的通道数量来填写 glsl 的值,可以是 r、g、b、a 等
  repeat: {
    x: 1,
    y: 1, // 透明度贴图在 x、y 方向的重复次数  
  }
})

当然,Material 类也可以自己创建材质对象,分缓存和一次性使用两种创建方法。

new Material({
  fabric: {
    type: 'MyOwnMaterial',
    // fabric 材质对象的其它参数  
  }
  // ... 其它参数
})
// 缓存后就可以这样用:
Material.fromType('MyOwnMaterial', /* uniforms */)

new Material({
  fabric: {
    // fabric 材质对象的其它参数  
  }
  // ... 其它参数
})

区别就在 fabric.type 参数,只要有 fabric.type,第一次创建就会缓存这个 fabric 材质,第二次就可以使用 fromType() 来访问缓存的材质了,并且不再需要传递完整的 fabric 对象,只需传递 type 和新的 uniforms 参数(如果需要更新)即可。

如果不传递 fabric.type 参数,那么创建的材质对象只能在生命周期内使用,CesiumJS 不会缓存,适合一次性使用。

创建好材质对象后,可以直接修改 uniform 的值完成动态更新效果,例如:

// 赋予一个新材质
primitive.appearance.material = Material.fromType('Image')
// 在某一处动态更新贴图
primitive.appearance.material.uniforms.image = '新贴图的地址'

2.4. Fabric 材质中级(GLSL表达式、嵌套材质)

Fabric 材质规范允许在创建材质对象时,使用更细致的规则。当然可以使用完整的着色器函数代码,但是为了简单易用,CesiumJS 在“完整着色器函数”和“JavaScript API” 之间还设计了一层“GLSL表达式”来定制各个 成分组件(components,下文简称成分)

举例:

new Material({
  fabric: {
    type: 'MyComponentsMaterial',
    components: {
      diffuse: 'vec3(1.0, 0.0, 0.0)',
      specular: '0.1',
      alpha: '0.6',
    }  
  }
})

从这个 components 对象可以看出,这一个材质对象设定了三个成分:

  • diffuse,漫反射颜色,设为了 GLSL 表达式 vec3(1.0, 0.0, 0.0),即纯红色

  • specular,高光强度,设为了 0.1

  • alpha,透明度,设为了 0.6

这些都会合成到完整的着色器代码的对应分量上。

那么,这个 components 对象允许拥有哪些成分呢?这受限制于内置的 GLSL 结构体的成员:

struct czm_material {
  vec3 diffuse;
  float specular;
  float shininess;
  vec3 normal;
  vec3 emission;
  float alpha;
}

也就是说,diffuse(漫反射颜色)、specular(高光强度)、shininess(镜面反射强度)、normal(相机或眼坐标中的法线)、emission(自发光颜色)、alpha(透明度)这 6 个都可以出现在 components 对象中,其值是字符串,必须是可以赋予给 GLSL 结构体对应成员的表达式。

什么意思呢?除了上面的举例 diffuse: 'vec3(1.0, 0.0, 0.0)' 外,任意的 GLSL 内置类型、内置函数均可使用,只要是表达式均可,例如 mixcossintantexture2D(GLSL100)、texture(GLSL300)。

举例,如果你在 uniforms 中传递了一个自定义的 image 作为纹理,那么你可以在 components.diffuse 中调用 texture2D 函数对这个 image 变量进行纹理采样:

const someMaterialFabric = {
  type: 'OurDiffuseMap',
  uniforms: {
    image: 'czm_defaultImage' // 'czm_defaultImage' 是一个内置的 1x1 贴图
  },
  components: {
    diffuse: 'texture2D(image, materialInput.st).rgb'
  }
}

其中,texture(image, materialInput.st).rgbimage 就是 uniforms.imagematerialInput.st 是来自输入变量 materialInput 的纹理坐标。至于 materialInput,之后讲解 fabric.source 完整版着色器代码的用法时会介绍。

我觉得如果要写一些更复杂的表达式,不如直接进阶用法,写完整的着色器更灵活,components 适合最简单的表达式。

fabric 对象上已经介绍了 3 个成员了,即 fabric.typefabric.uniformsfabric.components,那么现在介绍第四个 —— 允许材质组合的 fabric.materials 成员。

幸运的是,官方的文档有举简单的例子,我就直接抄过来说明了:

const combineFabric = {
  type: 'MyCombineMaterial',
  materials: {
    diffuseMaterial: {
      type: 'DiffuseMap'
    },
    specularMaterial: {
      type: 'SpecularMap'
    }
  },
  components: {
    diffuse: 'diffuseMaterial.diffuse',
    specular: 'specularMaterial.specular'
  }
}

materials 中定义的两个子材质 diffuseMaterialspecularMaterial 也是满足 Fabric 规范的,这里直接用了两个内置材质(漫反射贴图材质、高光贴图材质)。定义在 materials 中,然后在 components 和将来要介绍的 fabric.source 着色器完整代码中都能用了。

例如,这里的 components.diffuse 设为了 diffuseMaterial.diffuse,实际上 diffuseMaterial 就是一个 CesiumJS 内置的 GLSL 结构体变量,在上文提过,结构体为 czm_material

子材质的 uniforms 也和普通材质的一样可以更新:

const m = Material.fromType('MyCombineMaterial')
primitive.appearance.material = m

m.materials.diffuseMaterial.uniforms.image = 'diffuseMap.png'
m.materials.specularMaterial.uniforms.image = 'specularMap.png'

通常不建议嵌套太深,容易造成性能问题。

中段小结

至此,已经介绍了 Primitive API 中的两大 API —— Geometry APIAppearance + Material API 的入门和中阶使用,并使用一些简单的代码实例辅助说明。到这里为止已经可以运用内置的几何、材质外观来做一些入门的高性能渲染了,但是未来的你一定不满足于此,那就需要更进阶的用法 —— 完整的着色器编写,去控制几何体在顶点和片元着色阶段的细节。

受限于篇幅,进阶内容于下一篇讲解。

有关CesiumJS PrimitiveAPI 高级着色入门 - 从参数化几何与 Fabric 材质到着色器 - 上篇的更多相关文章

  1. ruby-on-rails - 如何在 ruby​​ 中使用两个参数异步运行 exe? - 2

    exe应该在我打开页面时运行。异步进程需要运行。有什么方法可以在ruby​​中使用两个参数异步运行exe吗?我已经尝试过ruby​​命令-system()、exec()但它正在等待过程完成。我需要用参数启动exe,无需等待进程完成是否有任何ruby​​gems会支持我的问题? 最佳答案 您可以使用Process.spawn和Process.wait2:pid=Process.spawn'your.exe','--option'#Later...pid,status=Process.wait2pid您的程序将作为解释器的子进程执行。除

  2. ruby - RSpec - 使用测试替身作为 block 参数 - 2

    我有一些Ruby代码,如下所示:Something.createdo|x|x.foo=barend我想编写一个测试,它使用double代替block参数x,这样我就可以调用:x_double.should_receive(:foo).with("whatever").这可能吗? 最佳答案 specify'something'dox=doublex.should_receive(:foo=).with("whatever")Something.should_receive(:create).and_yield(x)#callthere

  3. ruby - 如何在 Ruby 中拆分参数字符串 Bash 样式? - 2

    我正在为一个项目制作一个简单的shell,我希望像在Bash中一样解析参数字符串。foobar"helloworld"fooz应该变成:["foo","bar","helloworld","fooz"]等等。到目前为止,我一直在使用CSV::parse_line,将列分隔符设置为""和.compact输出。问题是我现在必须选择是要支持单引号还是双引号。CSV不支持超过一个分隔符。Python有一个名为shlex的模块:>>>shlex.split("Test'helloworld'foo")['Test','helloworld','foo']>>>shlex.split('Test"

  4. ruby - 检查方法参数的类型 - 2

    我不确定传递给方法的对象的类型是否正确。我可能会将一个字符串传递给一个只能处理整数的函数。某种运行时保证怎么样?我看不到比以下更好的选择:defsomeFixNumMangler(input)raise"wrongtype:integerrequired"unlessinput.class==FixNumother_stuffend有更好的选择吗? 最佳答案 使用Kernel#Integer在使用之前转换输入的方法。当无法以任何合理的方式将输入转换为整数时,它将引发ArgumentError。defmy_method(number)

  5. ruby-on-rails - 在默认方法参数中使用 .reverse_merge 或 .merge - 2

    两者都可以defsetup(options={})options.reverse_merge:size=>25,:velocity=>10end和defsetup(options={}){:size=>25,:velocity=>10}.merge(options)end在方法的参数中分配默认值。问题是:哪个更好?您更愿意使用哪一个?在性能、代码可读性或其他方面有什么不同吗?编辑:我无意中添加了bang(!)...并不是要询问nobang方法与bang方法之间的区别 最佳答案 我倾向于使用reverse_merge方法:option

  6. ruby - 定义方法参数的条件 - 2

    我有一个只接受一个参数的方法:defmy_method(number)end如果使用number调用方法,我该如何引发错误??通常,我如何定义方法参数的条件?比如我想在调用的时候报错:my_method(1) 最佳答案 您可以添加guard在函数的开头,如果参数无效则引发异常。例如:defmy_method(number)failArgumentError,"Inputshouldbegreaterthanorequalto2"ifnumbereputse.messageend#=>Inputshouldbegreaterthano

  7. ruby - rails 3 redirect_to 将参数传递给命名路由 - 2

    我没有找到太多关于如何执行此操作的信息,尽管有很多关于如何使用像这样的redirect_to将参数传递给重定向的建议:action=>'something',:controller=>'something'在我的应用程序中,我在路由文件中有以下内容match'profile'=>'User#show'我的表演Action是这样的defshow@user=User.find(params[:user])@title=@user.first_nameend重定向发生在同一个用户Controller中,就像这样defregister@title="Registration"@user=Use

  8. ruby - 字符串文字中的转义状态作为 `String#tr` 的参数 - 2

    对于作为String#tr参数的单引号字符串文字中反斜杠的转义状态,我觉得有些神秘。你能解释一下下面三个例子之间的对比吗?我特别不明白第二个。为了避免复杂化,我在这里使用了'd',在双引号中转义时不会改变含义("\d"="d")。'\\'.tr('\\','x')#=>"x"'\\'.tr('\\d','x')#=>"\\"'\\'.tr('\\\d','x')#=>"x" 最佳答案 在tr中转义tr的第一个参数非常类似于正则表达式中的括号字符分组。您可以在表达式的开头使用^来否定匹配(替换任何不匹配的内容)并使用例如a-f来匹配一

  9. ruby-on-rails - 如何生成传递一些自定义参数的 `link_to` URL? - 2

    我正在使用RubyonRails3.0.9,我想生成一个传递一些自定义参数的link_toURL。也就是说,有一个articles_path(www.my_web_site_name.com/articles)我想生成如下内容:link_to'Samplelinktitle',...#HereIshouldimplementthecode#=>'http://www.my_web_site_name.com/articles?param1=value1¶m2=value2&...我如何编写link_to语句“alàRubyonRailsWay”以实现该目的?如果我想通过传递一些

  10. 旋转矩阵的几何意义 - 2

    点向量坐标矩阵的几何意义介绍旋转矩阵的几何含义之前,先介绍一下点向量坐标矩阵的几何含义点:在一维空间下就是一个标量,如同一条直线上,以任意某一个位置为0点,以一定的尺度间隔为1,2,3...,相反方向为-1,-2,-3...;如此就形成了一维坐标系,这时候任何一个点都可以用一个数值表示,如点p1=5,即即从原点出发沿着x轴正方向移动5个尺度;点p2=-3,负方向移动3个尺度;     在一维坐标系上过原点做垂直于一维坐标系的直线,则形成了二维坐标系,此时描述一个点需要两个数值来表示点p3=(3,2),即从原点出发沿着x轴正方向移动3个尺度,在此基础上沿着y轴正方向移动两个尺度的位置就是点p3。

随机推荐