博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
swift设置启动图不现实_如何在Swift中轻松开始使用增强现实
阅读量:2522 次
发布时间:2019-05-11

本文共 19981 字,大约阅读时间需要 66 分钟。

swift设置启动图不现实

by Ranadhir Dey

由Ranadhir Dey

如何在Swift中轻松开始使用增强现实 (How to get started with augmented reality in Swift, the easy way)

If you look around, this is the golden era of technology. Every keynote adds something new to the existing stack of technologies. It’s exciting to see how these emerging technologies have enhanced the boundaries of our imagination. As a developer, we must be proud that we are the firsthand users of these technologies.

如果环顾四周,这就是技术的黄金时代。 每个主题演讲都为现有技术堆栈增加了新的内容。 看到这些新兴技术如何增强我们的想象力范围,真是令人兴奋。 作为开发人员,我们必须为我们是这些技术的第一手用户而感到自豪。

But every new technology comes with quite a steep learning curve. You just can’t watch a keynote or a video on Youtube and start developing an app. But the good news is that, with AR in Swift, it’s remarkably easy to work with basic AR apps. Apple has done most of the heavy lifting for you. Follow along and you’ll see how easy it can be.

但是每一项新技术都带有相当陡峭的学习曲线。 您只是无法在YouTube上观看主题演讲或视频而开始开发应用程序。 但是好消息是,借助Swift中的AR,使用基本的AR应用程序非常容易。 苹果为您完成了大部分繁重的工作。 继续学习,您会发现它很容易。

让我们深入... (Let’s dig in…)

In this tutorial, we will learn the necessary tools and techniques of AR in Swift that will allow us to create an app that decorates your floor with some cool floor tiles and wooden textures. The finished app will look something like this:

在本教程中,我们将学习Swift中AR的必要工具和技术,这将使我们能够创建一个应用,以一些凉爽的地砖和木质纹理装饰您的地板。 完成的应用程序将如下所示:

Let’s start by creating a single view application in Xcode and name it Home Decor.

让我们首先在Xcode中创建一个单一视图应用程序并将其命名为Home Decor。

添加摄像头权限 (Adding camera permissions)

Now the very first thing we will do is to navigate to the info.plist file and enable camera usage. Camera capability is the first thing you need for an AR app. Find the Camera Usage Description key, like the below image, and give it a suitable message. This message will show up in the very first launch of the app while asking for the camera permissions from the user.

现在,我们要做的第一件事是导航到info.plist文件并启用相机使用。 相机功能是AR应用程序的第一件事。 找到“摄像机使用情况描述”键,如下图所示,并提供适当的消息。 该消息将在应用程序首次启动时显示,同时要求用户提供摄像头许可。

向应用程序添加ARKit功能 (Adding ARKit Capabilities to the app)

Go to Main.storyboard. Drag and drop an ARKit SceneKit View on the ViewController and pin the ARSCNView to the edges of the ViewController.

转到Main.storyboard。 将ARKit SceneKit视图拖放到ViewController上,然后将ARSCNView固定到ViewController的边缘。

Create a IBOutlet to the ViewController class, and name it sceneView. As soon as you do that, an error stating undeclared ARSCNView, will popup, as our view controller doesn’t recognise anything of type ARSCNView. To resolve this, and to use other ARKit features, we need to import ARKit into the view controller.

为ViewController类创建一个IBOutlet,并将其命名为sceneView。 一旦执行此操作 就会弹出一个错误,指出未声明的ARSCNView ,因为我们的视图控制器无法识别ARSCNView类型的任何内容。 要解决此问题并使用其他ARKit功能,我们需要将ARKit导入视图控制器。

Now move from storyboard to the view controller.swift file. Declare a property of type ARWorldTrackingConfiguration before the viewDidLoad() method and name it config. And our view controller will look like this (I’ve removed the didReceiveMemoryWarning method):

现在从情节提要板移到视图controller.swift文件。 在viewDidLoad()方法之前声明一个ARWorldTrackingConfiguration类型的属性,并将其命名为config。 我们的视图控制器将如下所示(我已经删除了didReceiveMemoryWarning方法):

import UIKitimport ARKit
class ViewController: UIViewController {
@IBOutlet weak var sceneView: ARSCNView!let config = ARWorldTrackingConfiguration()
override func viewDidLoad() {super.viewDidLoad()}

允许调试 (Allow debugging)

This config variable will determine the configurations of the scene session. We will see it’s usage later in the section. Now, in the viewDidLoad method after super.viewDidLoad(), add the following:

此配置变量将确定场景会话的配置。 我们将在本节的后面部分看到它的用法。 现在,在super.viewDidLoad()之后的viewDidLoad方法中,添加以下内容:

sceneView.debugOptions = [ARSCNDebugOptions.showFeaturePoints, ARSCNDebugOptions.showWorldOrigin]

Here we are enabling debug options for our sceneView, which is nothing but the camera view with the capabilities of AR framework. ARSCNDebugOptions.showWorldOrigin will display world origin on the screen. This will help us find the reference point of all other positions. ARSCNDebugOptions.showFeaturePoints will display all the points on the screen which the AR camera has recognised in the surroundings.

在这里,我们为我们的sceneView启用调试选项,它不过是具有AR框架功能的摄影机视图。 ARSCNDebugOptions.showWorldOrigin将在屏幕上显示世界原点。 这将帮助我们找到所有其他位置的参考点。 ARSCNDebugOptions.showFeaturePoints将在屏幕上显示AR摄像机已经识别出的所有点。

Now to start the AR session, we need to run a session in our sceneView with the configurations mentioned in the config variable. Just below sceneView.debugOptions line, write:

现在开始AR会话,我们需要使用config变量中提到的配置在sceneView中运行一个会话。 在sceneView.debugOptions行下面,编写:

sceneView.session.run(config)

Now run the app on your device (not on a simulator, as it doesn’t have the camera). The alert asking for camera permissiosn with the message you wrote will show up, and you need allow it. Wait for a bit while it loads the world origin.

现在,在您的设备上运行该应用程序(由于没有相机,因此不能在模拟器上运行)。 将会显示要求您提供摄影机权限的警报,您需要允许它。 等待加载世界原点的时间。

If you are here, you have already have an AR app running. Congratulations!

如果您在这里,您已经在运行AR应用程序。 恭喜你!

AR轴如何工作 (How AR Axes work)

The red bar or X axis is used to position objects left or right of the world origin. The green bar or Y axis is used to position objects to the top or bottom of the world origin. And the blue bar or Z axis is used to determine how close or far an object will be placed from the world origin.

红色条或X轴用于将对象定位在世界原点的左侧或右侧。 绿条或Y轴用于将对象放置在世界原点的顶部或底部。 蓝色条或Z轴用于确定物体距世界原点的距离。

A positive value of X will position an object to the right of the world origin, and negative will place it on the left. Positive for Y will place it on top and negative will place it on the bottom of the world origin. Positive for Z will place it nearer, and negative will place it farther from the world origin.

X的正值会将对象放置在世界原点的右侧,负值将其放置在左侧。 Y的正值会将其放在世界原点的顶部,负数的它将使其放在世界原点的底部。 Z的正值将使其更接近,Z的负值将使其离世界原点更远。

添加虚拟对象 (Adding a virtual object)

Let’s add some virtual objects to the scene. 3D capsule would be a good choice. Declare a capsuleNode of type and give it a geometry of . Give it a height of 0.1 meter, and radius of 0.03 meters.

让我们向场景中添加一些虚拟对象。 3D胶囊将是一个不错的选择。 声明一个SCNNode类型的并为其指定一个的几何形状。 使其高度为0.1米,半径为0.03米。

let capsuleNode = SCNNode(geometry: SCNCapsule(capRadius: 0.03, height: 0.1

Now position it 0.1 meter left of the world origin, 0.1 meter above the world origin, and 0.1 meter away from the world origin:

现在,将其定位在世界原点左侧0.1米,世界原点上方0.1米和世界原点0.1米的位置:

capsuleNode.position = SCNVector3(0.1, 0.1, -0.1)

Now, add the node to scene:

现在,将节点添加到场景:

sceneView.scene.rootNode.addChildNode(capsuleNode)

The sceneView contains a scene which is responsible for holding all the 3D objects in SCNNode format that will form the 3D scene. We are adding the capsule to the root node of the scene. Root node’s position is exactly aligned to the position of the world origin. That means its position is (0,0,0).

sceneView包含一个场景,该场景负责以SCNNode格式保存将构成3D场景的所有3D对象。 我们正在将胶囊添加到场景的根节点。 根节点的位置与世界原点的位置完全对齐。 这意味着它的位置是(0,0,0)。

Currently, our viewDidLoad method looks like this:

当前,我们的viewDidLoad方法看起来像这样:

override func viewDidLoad() {
super.viewDidLoad()
sceneView.debugOptions = [ARSCNDebugOptions.showFeaturePoints, ARSCNDebugOptions.showWorldOrigin]
sceneView.session.run(config)
let capsuleNode = SCNNode(geometry: SCNCapsule(capRadius: 0.03, height: 0.1))
capsuleNode.position = SCNVector3(0.1, 0.1, -0.1)
sceneView.scene.rootNode.addChildNode(capsuleNode)
}

Now run the app.

现在运行该应用程序。

Cool! We have just positioned a virtual object in the real world. You can play with different positions and different to explore more. Now let’s rotate the capsule 90 degree around the Z axis so that it lies flat on the X axis and changes its colour to blue.

凉! 我们刚刚在现实世界中放置了一个虚拟对象。 您可以使用不同的位置和不同的进行探索。 现在,让胶囊绕Z轴旋转90度,使其平放在X轴上,并将其颜色更改为蓝色。

欧拉角 (Euler Angles)

Euler Angles are responsible for a SCNNode’s display angle. We will see how to use it to rotate the capsule.

欧拉角负责SCNNode的显示角度。 我们将看到如何使用它旋转胶囊。

Every SCNGeometry can have materials added to it, which defines the appearance of the geometry. Materials have a diffuse property which, when set, spreads its content all over the geometry.

每个SCNGeometry都可以添加材料,以定义几何外观。 材料具有扩散属性,设置该属性后,其内容将分散到整个几何图形中。

In viewDidLoad, add the below lines after you set the position of the capsule.

在viewDidLoad中,设置胶囊的位置后添加以下几行。

capsuleNode.geometry?.firstMaterial?.diffuse.contents = UIColor.blue //1capsuleNode.eulerAngles = SCNVector3(0,0,Double.pi/2)//2

Here, in the first line, we are setting the blue colour to the very first material of the node which will spread across the capsule and will make it look blue. In line 2, we are setting the Z Euler angle to 90 degree radians. Finally, our view loads and looks like this:

在这里,在第一行中,我们将蓝色设置为节点的第一材料,该材料将散布在整个胶囊上并使它看起来是蓝色。 在第2行中,我们将Z Euler角设置为90度弧度。 最后,我们的视图加载并如下所示:

override func viewDidLoad() {
super.viewDidLoad()
sceneView.debugOptions = [ARSCNDebugOptions.showFeaturePoints, ARSCNDebugOptions.showWorldOrigin]
sceneView.session.run(config)
let capsuleNode = SCNNode(geometry: SCNCapsule(capRadius: 0.03, height: 0.1))
capsuleNode.position = SCNVector3(0.1, 0.1, -0.1)
capsuleNode.geometry?.firstMaterial?.diffuse.contents = UIColor.blue //1
capsuleNode.eulerAngles = SCNVector3(0,0,Double.pi/2)//2
sceneView.scene.rootNode.addChildNode(capsuleNode)
}

Now run the app.

现在运行该应用程序。

Great! A blue coloured sleeping capsule on the wall! You can even add textures as diffuse contents to make an object look more realistic. We will use that in the next section when we place the tiles’ textures on the floor.

大! 墙上的蓝色睡眠胶囊! 您甚至可以将纹理添加为漫反射内容,以使对象看起来更逼真。 在下一部分中,当我们将瓷砖的纹理放置在地板上时,将使用它。

Now that we have successfully placed virtual objects on the real world, it’s time to decorate our real floor with virtual floor-tiles. To achieve the floor effect, we will use a geometry. SCNPlane doesn’t have any depth like other 3D geometries, which makes it a perfect fit for our app.

现在我们已经成功地将虚拟对象放置在现实世界中,是时候使用虚拟地板砖装饰我们的真实地板了。 为了实现地板效果,我们将使用几何。 SCNPlane没有其他3D几何形状的深度,这使其非常适合我们的应用程序。

ARSCENE查看代表 (ARSCENEView Delegates)

Before starting the floor detection, we will explore some delegate methods of our sceneView to understand what capabilities we are equipped with to interact with an ongoing AR session.

在开始进行楼层检测之前,我们将探索SceneView的一些委托方法,以了解我们具备与正在进行的AR会话进行交互的功能。

func renderer(SCNSceneRenderer, didAdd: SCNNode, for: ARAnchor)

Whenever we move or tilt our device with an AR session on in it, the ARKit tries to find different ARAnchors in the surroundings. An contains information about a real world position and orientation that can be used to place an object.

每当我们移动或倾斜带有AR会话的设备时,ARKit都会尝试在周围环境中找到不同的ARAnchor。 包含有关可用于放置对象的现实世界位置和方向的信息。

Once a different anchor is found, a new node gets added to the scene with the same information to accommodate this newly found anchor. This delegate method will inform us about that. We will be using it to find all the positions on the floor to place the tiles.

一旦找到不同的锚点,便会使用相同的信息将新节点添加到场景中,以容纳此新发现的锚点。 此委托方法将告知我们有关此信息。 我们将使用它在地板上找到所有放置瓷砖的位置。

func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor)

Most of the time, all the nodes that get added from the anchors belong to the same object. Let’s say you are moving around the floor and the device finds a number of anchors at different positions. It tries to add all the nodes for those anchors, as it thinks that all these anchors belong to different objects.

大多数情况下,从锚中添加的所有节点都属于同一对象。 假设您在地板上四处走动,设备在不同位置找到了许多锚。 它试图添加所有这些锚点的节点,因为它认为所有这些锚点属于不同的对象。

But ARKit eventually recognises that all of them belong to the same floor, so it updates the very first floor node by appending dimensions of other duplicate nodes. This delegate method will inform us about that.

但是ARKit最终认识到它们都属于同一层,因此它通过附加其他重复节点的尺寸来更新第一层节点。 此委托方法将告知我们有关此信息。

func renderer(SCNSceneRenderer, didRemove: SCNNode, for: ARAnchor)

After updating the first unique node with dimensions of all other duplicate nodes, ARKit removes all the duplicate nodes and the delegate method notifies us. We will be using all of the above delegate methods in our app (and their purpose will become clearer).

用所有其他重复节点的尺寸更新第一个唯一节点后,ARKit删除所有重复节点,然后委托方法通知我们。 我们将在我们的应用程序中使用上述所有委托方法(其目的将变得更加清楚)。

平面检测 (Plane detection)

Presently, our scene is trying to gather all anchors that it comes across, as that is the default behaviour. But since a floor is a horizontal surface, we are only interested in anchors that are on horizontal planes. So, go back to our viewDidLoad method and write the below code before running the session (that is before sceneView.session.run(config)) line.

目前,我们的场景正在尝试收集遇到的所有锚点,因为这是默认行为。 但是由于地板是水平表面,所以我们只对水平面上的锚感兴趣。 因此,返回到我们的viewDidLoad方法并运行会话之前(即SceneView.session.run(config)之前)编写以下代码。

config.planeDetection = .horizontal

In the viewDidLoad method, you can remove everything after sceneView.session.run(config) as that was for placing the capsule on screen and we don’t need that anymore. Since we will be using all the above mentioned delegate methods, we need to make our viewController a delegate of the sceneView. Before the closing brace of viewDidLoad() method, add the below line.

在viewDidLoad方法中,您可以删除sceneView.session.run(config)之后的所有内容,因为这是用于将胶囊显示在屏幕上的,我们不再需要它。 由于我们将使用上述所有委托方法,因此需要将viewController设为sceneView的委托。 在viewDidLoad()方法的右括号之前,添加以下行。

sceneView.delegate = self

You should get an error now, as our view controller still is not conforming the sceneView delegate. To implement this, let’s create an extension of the view controller at the end of the ViewController.swift file.

您现在应该得到一个错误,因为我们的视图控制器仍然不符合SceneView委托。 为了实现这一点,让我们在ViewController.swift文件的末尾创建视图控制器的扩展。

extension ViewController:ARSCNViewDelegate{}

The didAdd SCNNode delegate method will get fired every time a part of the floor is discovered and a new node gets added to the scene based on the anchor. Within this method, we will create a floor node and will add it as a child of the recently added node in the position of the anchor.

每当发现一部分楼层并根据锚点将新节点添加到场景时,didAdd SCNNode委托方法将被触发。 在此方法中,我们将创建一个地板节点,并将其添加为锚点位置中最近添加的节点的子节点。

can be of four different types to solve four different purposes. Here we are only interested in ARPlaneAnchor which detects the horizontal or vertical planes.

可以采用四种不同的类型来解决四种不同的目的。 在这里,我们只对ARPlaneAnchor感兴趣,它可以检测水平或垂直平面。

创建AR地板节点 (Creating AR floor nodes)

Let’s create a function that would receive an ARPlaneAnchor as a parameter, create a floor node at the anchor’s position, and return it.

让我们创建一个函数,该函数将接收ARPlaneAnchor作为参数,在锚点的位置创建一个地板节点,然后将其返回。

func createFloorNode(anchor:ARPlaneAnchor) ->SCNNode{
let floorNode = SCNNode(geometry: SCNPlane(width: CGFloat(anchor.extent.x), height: CGFloat(anchor.extent.z))) //1
floorNode.position=SCNVector3(anchor.center.x,0,anchor.center.z)                                               //2
floorNode.geometry?.firstMaterial?.diffuse.contents = UIColor.blue                                             //3
floorNode.geometry?.firstMaterial?.isDoubleSided = true                                                        //4
floorNode.eulerAngles = SCNVector3(Double.pi/2,0,0)                                                    //5
return floorNode                                                                                               //6
}

Let’s go through the function line by line and discuss it in more detail. Please follow each line’s description, as it’s the trickiest part.

让我们逐行浏览该功能并进行更详细的讨论。 请按照每一行的说明进行操作,因为这是最棘手的部分。

1. We are creating a node with a geometry of SCNPlane which has the size of the anchor. ARPlaneAnchor’s extent holds the position information. The fact that the extent.z has been used as height and not extent.y, might be a little confusing. If you visualise that a 3D cube is placed on a floor and you want to make it flat along a 2D surface, you would change the y to zero and it would go flat. Now, to get the length of this 2D surface, you would consider the z, wouldn’t you? Our floor is flat, so we need a flat node not a cube.

1.我们正在创建一个几何图形为SCNPlane的节点,该节点具有锚点的大小。 ARPlaneAnchor的范围保存位置信息。 程度上.z被用作高度而不是范围.y的事实可能有点令人困惑。 如果可视化3D立方体放置在地板上并且想要使其沿2D平面平坦,则可以将y更改为零,然后将其平坦。 现在,要获得2D曲面的长度,您会考虑z,不是吗? 我们的地板是平坦的,因此我们需要一个平坦的节点而不是一个立方体。

2. We are setting the position of the node. As we don’t need any elevation, we make y zero.

2.我们正在设置节点的位置。 由于我们不需要任何高程,因此使y为零。

3. Set the floor colour to blue.

3.将地板颜色设置为蓝色。

4. The material colour will be displayed only on one side unless we specifically mention it is double-sided.

4.除非我们特别提到材料颜色是双面的,否则材料颜色将仅在一侧显示。

5. By default, the plane will be placed vertically. To make it horizontal, we need to rotate it by 90 degrees.

5.默认情况下,飞机将垂直放置。 要使其水平,我们需要将其旋转90度。

实现委托方法 (Implementing the delegate methods)

Now, let’s implement the didAdd SCNNode delegate method.

现在,让我们实现didAdd SCNNode委托方法。

func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
guard let planeAnchor = anchor as? ARPlaneAnchor else {return} //1
let planeNode = createFloorNode(anchor: planeAnchor) //2
node.addChildNode(planeNode) //3
}

In line 1, we are checking if the anchor is a ARPlaneAnchor, since we would only deal with this type of anchor.

在第1行中,我们正在检查锚点是否为ARPlaneAnchor,因为我们将只处理此类锚点。

In line 2, a new node is getting created based on the anchor. In line 3, it’s getting added to the node.

在第2行中,将基于锚创建一个新节点。 在第3行中,它已添加到节点中。

Now in the didUpdate SCNNode delegate, we will delete all our floor nodes. We’ll do this because the dimensions of the current node have been changed and the old floor nodes won’t match. Then we will again add a fresh floor node to this updated node.

现在,在didUpdate SCNNode委托中,我们将删除所有楼层节点。 我们这样做是因为当前节点的尺寸已更改,并且旧楼层节点将不匹配。 然后,我们将再次向该更新的节点添加一个新的发言权节点。

func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
guard let planeAnchor = anchor as? ARPlaneAnchor else {return}
node.enumerateChildNodes { (node, _) in
node.removeFromParentNode()
}
let planeNode = createFloorNode(anchor: planeAnchor)
node.addChildNode(planeNode)
}

In didRemove SCNNode delegate method, we want to clean out all our junk nodes in a civilized manner.

在didRemove SCNNode委托方法中,我们希望以一种文明的方式清除所有垃圾节点。

func renderer(_ renderer: SCNSceneRenderer, didRemove node: SCNNode, for anchor: ARAnchor) {
guard let _ = anchor as? ARPlaneAnchor else {return}
node.enumerateChildNodes { (node, _) in
node.removeFromParentNode()
}
}

Phew! Thats it! Run the app.

! 而已! 运行应用程序。

添加平铺效果 (Adding the tile effect)

Wait, what? A blue floor? No, we are not completely done yet. Just a small change and we will have a stunning floor!

等一下 蓝色的地板? 不,我们还没有完全完成。 只需很小的改动,我们的地板就会令人惊叹!

To change the blue floor to tiles, we need a texture. Let’s google for a floor tile texture. I searched for “wooden floor texture” and found some beautiful texture images. Save any of them on your Mac and drag it to the Assets.xcassets.

要将蓝色地板更改为瓷砖,我们需要一个纹理。 让我们用谷歌搜索地板砖的纹理。 我搜索“木地板纹理”,发现了一些漂亮的纹理图像。 将其中任何一个保存在Mac上,然后将其拖到Assets.xcassets。

I named it WoodenFloorTile. You can name it whatever you want. Back to the ViewController.swift file again. In the createFloorNode function, instead of setting UIColor.blue as diffuse content, make it an UIImage with the name you have given to the image in the asset folder.

我将其命名为WoodenFloorTile。 您可以随意命名。 再次回到ViewController.swift文件。 在createFloorNode函数中,不要将UIColor.blue设置为分散内容,而应使其成为一个UIImage,其名称与资产文件夹中的图像相同。

floorNode.geometry?.firstMaterial?.diffuse.contents = UIImage(named: "WoodenFloorTile")

Now run the app, and wait until the world origin loads. Once the floor is detected, move around to update the node information.

现在运行该应用程序,并等待世界原点加载。 一旦检测到地板,就四处移动以更新节点信息。

Wow, You really have a gorgeous floor! You can download multiple textures and place them in a listView. This allows you to change the floor based on selected texture, as it was shown in the first part.

哇,您的地板真的很漂亮! 您可以下载多个纹理并将其放置在listView中。 如第一部分所示,这使您可以根据选定的纹理更改地板。

Now that you have a nice floor, you must be missing some nice furnitures to give your room a great look! We will work on that later on.

现在您已经有了一个漂亮的地板,现在您必须缺少一些漂亮的家具才能让您的房间看起来更漂亮! 我们将在稍后进行处理。

翻译自:

swift设置启动图不现实

转载地址:http://yckzd.baihongyu.com/

你可能感兴趣的文章
jxl写入excel实现数据导出功能
查看>>
linux文件目录类命令|--cp指令
查看>>
.net MVC 404错误解决方法
查看>>
linux系统目录结构
查看>>
学习进度
查看>>
使用Postmark测试后端存储性能
查看>>
NSTextView 文字链接的定制化
查看>>
第五天站立会议内容
查看>>
最短路径(SP)问题相关算法与模板
查看>>
js算法之最常用的排序
查看>>
Python——交互式图形编程
查看>>
经典排序——希尔排序
查看>>
团队编程项目作业2-团队编程项目代码设计规范
查看>>
英特尔公司将停止910GL、915GL和915PL芯片组的生产
查看>>
Maven配置
查看>>
HttpServletRequest /HttpServletResponse
查看>>
SAM4E单片机之旅——24、使用DSP库求向量数量积
查看>>
从远程库克隆库
查看>>
codeforces Unusual Product
查看>>
hdu4348 - To the moon 可持久化线段树 区间修改 离线处理
查看>>