Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

322

323

324

325

326

327

328

329

330

331

332

333

334

335

336

337

338

339

340

341

342

343

344

345

346

347

348

349

350

351

352

353

354

355

356

357

358

359

360

361

362

363

364

365

366

367

368

369

370

371

372

373

374

375

376

377

378

379

380

381

382

383

384

385

386

387

388

389

390

391

392

393

394

395

396

397

398

399

400

401

402

403

404

405

406

407

408

409

410

411

412

413

414

415

416

417

418

419

420

421

422

423

424

425

426

427

428

429

430

431

432

433

434

435

436

437

438

439

440

441

442

443

444

445

446

447

448

449

450

451

452

453

454

455

456

457

458

459

460

461

462

463

464

465

466

467

468

469

470

471

472

473

474

475

476

477

478

479

480

481

482

483

484

485

486

487

488

489

490

491

492

493

494

495

496

497

498

499

500

501

502

503

504

505

506

507

508

509

510

511

512

513

514

515

516

517

518

519

520

521

522

523

524

525

526

527

528

529

530

531

532

533

534

535

536

537

538

539

540

541

542

543

544

545

546

547

548

549

550

551

552

553

554

555

556

557

558

559

560

561

562

563

564

565

566

567

568

569

570

571

572

573

574

575

576

577

578

579

580

581

582

583

584

585

586

587

588

589

590

591

592

593

594

r""" 

Adaptive refinement code for 3d surface plotting 

 

AUTHOR: 

 

- Tom Boothby -- Algorithm design, code 

- Joshua Kantor -- Algorithm design 

- Marshall Hampton -- Docstrings and doctests 

 

.. TODO:: 

 

- Parametrizations (cylindrical, spherical) 

- Massive optimization 

""" 

 

########################################################################### 

# Copyright (C) 2007 Tom Boothby <boothby@u.washington.edu> 

# 2007 Joshua Kantor <jkantor@math.washington.edu> 

# 

# Distributed under the terms of the GNU General Public License (GPL) 

# http://www.gnu.org/licenses/ 

########################################################################### 

from __future__ import print_function 

 

from sage.plot.colors import hue 

from math import sqrt 

import random 

 

class Triangle: 

""" 

A graphical triangle class. 

""" 

def __init__(self,a,b,c,color=0): 

""" 

a, b, c : triples (x,y,z) representing corners on a triangle in 3-space. 

 

TESTS:: 

 

sage: from sage.plot.plot3d.tri_plot import Triangle 

sage: tri = Triangle([0,0,0],[-1,2,3],[0,2,0]) 

sage: tri._a 

[0, 0, 0] 

sage: tri.__init__([0,0,1],[-1,2,3],[0,2,0]) 

sage: tri._a 

[0, 0, 1] 

""" 

self._a = a 

self._b = b 

self._c = c 

self._color = color 

 

def str(self): 

""" 

Returns a string representation of an instance of the Triangle 

class of the form 

 

a b c color 

 

where a, b, and c are corner coordinates and color is the color. 

 

TESTS:: 

 

sage: from sage.plot.plot3d.tri_plot import Triangle 

sage: tri = Triangle([0,0,0],[-1,2,3],[0,2,0]) 

sage: print(tri.str()) 

[0, 0, 0] [-1, 2, 3] [0, 2, 0] 0 

""" 

return "%s %s %s %s"%(self._a, self._b, self._c, self._color) 

 

def set_color(self, color): 

""" 

This method will reset the color of the triangle. 

 

TESTS:: 

 

sage: from sage.plot.plot3d.tri_plot import Triangle 

sage: tri = Triangle([0,0,0],[-1,2,3],[0,2,1]) 

sage: tri._color 

0 

sage: tri.set_color(1) 

sage: tri._color 

1 

""" 

self._color = color 

 

def get_vertices(self): 

""" 

Returns a tuple of vertex coordinates of the triangle. 

 

TESTS:: 

 

sage: from sage.plot.plot3d.tri_plot import Triangle 

sage: tri = Triangle([0,0,0],[-1,2,3],[0,2,1]) 

sage: tri.get_vertices() 

([0, 0, 0], [-1, 2, 3], [0, 2, 1]) 

""" 

return (self._a, self._b, self._c) 

 

class SmoothTriangle(Triangle): 

""" 

A class for smoothed triangles. 

""" 

def __init__(self,a,b,c,da,db,dc,color=0): 

""" 

a, b, c : triples (x,y,z) representing corners on a triangle in 3-space 

da, db, dc : triples (dx,dy,dz) representing the normal vector at each point a,b,c 

 

TESTS:: 

 

sage: from sage.plot.plot3d.tri_plot import SmoothTriangle 

sage: t = SmoothTriangle([1,2,3],[2,3,4],[0,0,0],[0,0,1],[0,1,0],[1,0,0]) 

sage: t._a 

[1, 2, 3] 

""" 

self._a = a 

self._b = b 

self._c = c 

self._da = da 

self._db = db 

self._dc = dc 

self._color = color 

 

def str(self): 

""" 

Returns a string representation of the SmoothTriangle of the form 

 

a b c color da db dc 

 

where a, b, and c are the triangle corner coordinates, 

da, db, dc are normals at each corner, and color is the color. 

 

TESTS:: 

 

sage: from sage.plot.plot3d.tri_plot import SmoothTriangle 

sage: t = SmoothTriangle([1,2,3],[2,3,4],[0,0,0],[0,0,1],[0,1,0],[1,0,0]) 

sage: print(t.str()) 

[1, 2, 3] [2, 3, 4] [0, 0, 0] 0 [0, 0, 1] [0, 1, 0] [1, 0, 0] 

""" 

return "%s %s %s %s %s %s %s"%(self._a, self._b, self._c, self._color, self._da, self._db, self._dc) 

 

def get_normals(self): 

""" 

Returns the normals to vertices a, b, and c. 

 

TESTS:: 

 

sage: from sage.plot.plot3d.tri_plot import SmoothTriangle 

sage: t = SmoothTriangle([1,2,3],[2,3,4],[0,0,0],[0,0,1],[0,1,0],[2,0,0]) 

sage: t.get_normals() 

([0, 0, 1], [0, 1, 0], [2, 0, 0]) 

""" 

return (self._da, self._db, self._dc) 

 

 

class TriangleFactory: 

def triangle(self, a, b, c, color = None): 

""" 

Parameters: 

a, b, c : triples (x,y,z) representing corners on a triangle in 3-space 

 

Returns: 

a Triangle object with the specified coordinates 

 

TESTS:: 

 

sage: from sage.plot.plot3d.tri_plot import TriangleFactory 

sage: factory = TriangleFactory() 

sage: tri = factory.triangle([0,0,0],[0,0,1],[1,1,0]) 

sage: tri.get_vertices() 

([0, 0, 0], [0, 0, 1], [1, 1, 0]) 

""" 

if color is None: 

return Triangle(a,b,c) 

else: 

return Triangle(a,b,c,color) 

 

def smooth_triangle(self, a, b, c, da, db, dc, color = None): 

""" 

Parameters: 

 

- a, b, c : triples (x,y,z) representing corners on a triangle in 3-space 

- da, db, dc : triples (dx,dy,dz) representing the normal vector at each point a,b,c 

 

Returns: 

a SmoothTriangle object with the specified coordinates and normals 

 

TESTS:: 

 

sage: from sage.plot.plot3d.tri_plot import TriangleFactory 

sage: factory = TriangleFactory() 

sage: sm_tri = factory.smooth_triangle([0,0,0],[0,0,1],[1,1,0],[0,0,1],[0,2,0],[1,0,0]) 

sage: sm_tri.get_normals() 

([0, 0, 1], [0, 2, 0], [1, 0, 0]) 

""" 

if color is None: 

return SmoothTriangle(a,b,c,da,db,dc) 

else: 

return SmoothTriangle(a,b,c,da,db,dc,color) 

 

def get_colors(self, list): 

""" 

Parameters: 

list: an iterable collection of values which can be cast into colors 

-- typically an RGB triple, or an RGBA 4-tuple 

 

Returns: 

a list of single parameters which can be passed into the set_color 

method of the Triangle or SmoothTriangle objects generated by this 

factory. 

 

TESTS:: 

 

sage: from sage.plot.plot3d.tri_plot import TriangleFactory 

sage: factory = TriangleFactory() 

sage: factory.get_colors([1,2,3]) 

[1, 2, 3] 

""" 

return list 

 

 

 

class TrianglePlot: 

""" 

Recursively plots a function of two variables by building squares of 4 triangles, checking at 

every stage whether or not each square should be split into four more squares. This way, 

more planar areas get fewer triangles, and areas with higher curvature get more triangles. 

""" 

 

def str(self): 

""" 

Returns a string listing the objects in the instance of the TrianglePlot class. 

 

TESTS:: 

 

sage: from sage.plot.plot3d.tri_plot import TrianglePlot, TriangleFactory 

sage: tf = TriangleFactory() 

sage: t = TrianglePlot(tf, lambda x,y: x^3+y*x-1, (-1, 3), (-2, 100), max_depth = 4) 

sage: len(t.str()) 

68980 

""" 

return "".join([o.str() for o in self._objects]) 

 

def __init__(self, triangle_factory, f, min_x__max_x, min_y__max_y, g = None, 

min_depth=4, max_depth=8, num_colors = None, max_bend=.3): 

""" 

 

TESTS:: 

 

sage: from sage.plot.plot3d.tri_plot import TrianglePlot, TriangleFactory 

sage: tf = TriangleFactory() 

sage: t = TrianglePlot(tf, lambda x,y: x^2+y^2, (0, 1), (0, 1)) 

sage: t._f(1,1) 

2 

""" 

(min_x, max_x) = min_x__max_x 

(min_y, max_y) = min_y__max_y 

self._triangle_factory = triangle_factory 

self._f = f 

self._g = g 

self._min_depth = min_depth 

self._max_depth = max_depth 

self._max_bend = max_bend 

self._objects = [] 

if min(max_x - min_x, max_y - min_y) == 0: 

raise ValueError('Plot rectangle is really a line. Make sure min_x != max_x and min_y != max_y.') 

self._num_colors = num_colors 

if g is None: 

def fcn(x,y): 

return [self._f(x,y)] 

else: 

def fcn(x,y): 

return [self._f(x,y), self._g(x,y)] 

 

self._fcn = fcn 

 

 

# generate the necessary data to kick-start the recursion 

mid_x = (min_x + max_x)/2 

mid_y = (min_y + max_y)/2 

sw_z = fcn(min_x,min_y) 

nw_z = fcn(min_x,max_y) 

se_z = fcn(max_x,min_y) 

ne_z = fcn(max_x,max_y) 

mid_z = fcn(mid_x,mid_y) 

 

self._min = min(sw_z[0], nw_z[0], se_z[0], ne_z[0], mid_z[0]) 

self._max = max(sw_z[0], nw_z[0], se_z[0], ne_z[0], mid_z[0]) 

 

# jump in and start building blocks 

outer = self.plot_block(min_x, mid_x, max_x, min_y, mid_y, max_y, sw_z, nw_z, se_z, ne_z, mid_z, 0) 

 

# build the boundary triangles 

self.triangulate(outer.left, outer.left_c) 

self.triangulate(outer.top, outer.top_c) 

self.triangulate(outer.right, outer.right_c) 

self.triangulate(outer.bottom, outer.bottom_c) 

 

zrange = self._max - self._min 

if num_colors is not None and zrange != 0: 

colors = triangle_factory.get_colors([hue(float(i/num_colors)) for i in range(num_colors)]) 

 

for o in self._objects: 

vertices = o.get_vertices() 

avg_z = (vertices[0][2] + vertices[1][2] + vertices[2][2])/3 

o.set_color(colors[int(num_colors * (avg_z - self._min) / zrange)]) 

 

 

def plot_block(self, min_x, mid_x, max_x, min_y, mid_y, max_y, sw_z, nw_z, se_z, ne_z, mid_z, depth): 

""" 

Recursive triangulation function for plotting. 

 

First six inputs are scalars, next 5 are 2-dimensional lists, and the depth argument 

keeps track of the depth of recursion. 

 

TESTS:: 

 

sage: from sage.plot.plot3d.tri_plot import TrianglePlot, TriangleFactory 

sage: tf = TriangleFactory() 

sage: t = TrianglePlot(tf, lambda x,y: x^2 + y^2, (-1,1), (-1, 1), max_depth=3) 

sage: q = t.plot_block(0,.5,1,0,.5,1,[0,1],[0,1],[0,1],[0,1],[0,1],2) 

sage: q.left 

[[(0, 0, 0)], [(0, 0.500000000000000, 0.250000000000000)], [(0, 1, 0)]] 

""" 

 

if depth < self._max_depth: 

# recursion is still an option -- step in one last level if we're within tolerance 

# and just keep going if we're not. 

# assumption: it's cheap to build triangles, so we might as well use all the data 

# we calculate 

 

# big square boundary midpoints 

mid_w_z = self._fcn(min_x, mid_y) 

mid_n_z = self._fcn(mid_x, max_y) 

mid_e_z = self._fcn(max_x, mid_y) 

mid_s_z = self._fcn(mid_x, min_y) 

 

next_depth = depth+1 

if depth < self._min_depth: 

# midpoints locations of sub_squares 

qtr1_x = (min_x + mid_x)/2 

qtr1_y = (min_y + mid_y)/2 

qtr3_x = (mid_x + max_x)/2 

qtr3_y = (mid_y + max_y)/2 

 

sw_depth = next_depth 

nw_depth = next_depth 

se_depth = next_depth 

ne_depth = next_depth 

else: 

#compute the midpoint-to-corner vectors 

sw_v = (min_x - mid_x, min_y - mid_y, sw_z[0] - mid_z[0]) 

nw_v = (min_x - mid_x, max_y - mid_y, nw_z[0] - mid_z[0]) 

se_v = (max_x - mid_x, min_y - mid_y, se_z[0] - mid_z[0]) 

ne_v = (max_x - mid_x, max_y - mid_y, ne_z[0] - mid_z[0]) 

 

#compute triangle normal unit vectors by taking the cross-products 

#of the midpoint-to-corner vectors. always go around clockwise 

#so we're guaranteed to have a positive value near 1 when neighboring 

#triangles are parallel 

#However -- crossunit doesn't really return a unit vector. It returns 

#the length of the vector to avoid numerical instability when the 

#length is nearly zero -- rather than divide by nearly zero, we multiply 

#the other side of the inequality by nearly zero -- in general, this 

#should work a bit better because of the density of floating-point 

#numbers near zero. 

norm_w = crossunit(sw_v, nw_v) 

norm_n = crossunit(nw_v, ne_v) 

norm_e = crossunit(ne_v, se_v) 

norm_s = crossunit(se_v, sw_v) 

 

#compute the dot products of the triangle unit norms 

e_sw = norm_w[0]*norm_s[0] + norm_w[1]*norm_s[1] + norm_w[2]*norm_s[2] 

e_nw = norm_w[0]*norm_n[0] + norm_w[1]*norm_n[1] + norm_w[2]*norm_n[2] 

e_se = norm_e[0]*norm_s[0] + norm_e[1]*norm_s[1] + norm_e[2]*norm_s[2] 

e_ne = norm_e[0]*norm_n[0] + norm_e[1]*norm_n[1] + norm_e[2]*norm_n[2] 

 

if e_sw < self._max_bend*norm_s[3]*norm_w[3]: 

sw_depth = next_depth 

else: 

sw_depth = self._max_depth 

if e_nw < self._max_bend*norm_n[3]*norm_w[3]: 

nw_depth = next_depth 

else: 

nw_depth = self._max_depth 

if e_se < self._max_bend*norm_s[3]*norm_e[3]: 

se_depth = next_depth 

else: 

se_depth = self._max_depth 

if e_ne < self._max_bend*norm_n[3]*norm_e[3]: 

ne_depth = next_depth 

else: 

ne_depth = self._max_depth 

 

qtr1_x = min_x + (.325 + random.random()/4)*(mid_x-min_x) 

qtr3_x = mid_x + (.325 + random.random()/4)*(max_x-mid_x) 

qtr1_y = min_y + (.325 + random.random()/4)*(mid_y-min_y) 

qtr3_y = mid_y + (.325 + random.random()/4)*(max_y-mid_y) 

 

# function evaluated at the midpoints (possibly random) 

mid_sw_z = self._fcn(qtr1_x,qtr1_y) 

mid_nw_z = self._fcn(qtr1_x,qtr3_y) 

mid_se_z = self._fcn(qtr3_x,qtr1_y) 

mid_ne_z = self._fcn(qtr3_x,qtr3_y) 

 

 

self.extrema([mid_w_z[0], mid_n_z[0], mid_e_z[0], mid_s_z[0], mid_sw_z[0], mid_se_z[0], mid_nw_z[0], mid_sw_z[0]]) 

 

# recurse into the sub-squares 

sw = self.plot_block(min_x, qtr1_x, mid_x, min_y, qtr1_y, mid_y, sw_z, mid_w_z, mid_s_z, mid_z, mid_sw_z, sw_depth) 

nw = self.plot_block(min_x, qtr1_x, mid_x, mid_y, qtr3_y, max_y, mid_w_z, nw_z, mid_z, mid_n_z, mid_nw_z, nw_depth) 

se = self.plot_block(mid_x, qtr3_x, max_x, min_y, qtr1_y, mid_y, mid_s_z, mid_z, se_z, mid_e_z, mid_se_z, se_depth) 

ne = self.plot_block(mid_x, qtr3_x, max_x, mid_y, qtr3_y, max_y, mid_z, mid_n_z, mid_e_z, ne_z, mid_ne_z, ne_depth) 

 

# join the sub-squares 

self.interface(1, sw.right, sw.right_c, se.left, se.left_c) 

self.interface(1, nw.right, nw.right_c, ne.left, ne.left_c) 

self.interface(0, sw.top, sw.top_c, nw.bottom, nw.bottom_c) 

self.interface(0, se.top, se.top_c, ne.bottom, ne.bottom_c) 

 

#get the boundary information about the subsquares 

left = sw.left + nw.left[1:] 

left_c = sw.left_c + nw.left_c 

right = se.right + ne.right[1:] 

right_c = se.right_c + ne.right_c 

top = nw.top + ne.top[1:] 

top_c = nw.top_c + ne.top_c 

bottom = sw.bottom + se.bottom[1:] 

bottom_c = sw.bottom_c + se.bottom_c 

 

else: 

# just build the square we're in 

if self._g is None: 

sw = [(min_x,min_y,sw_z[0])] 

nw = [(min_x,max_y,nw_z[0])] 

se = [(max_x,min_y,se_z[0])] 

ne = [(max_x,max_y,ne_z[0])] 

c = [[(mid_x,mid_y,mid_z[0])]] 

else: 

sw = [(min_x,min_y,sw_z[0]),sw_z[1]] 

nw = [(min_x,max_y,nw_z[0]),nw_z[1]] 

se = [(max_x,min_y,se_z[0]),se_z[1]] 

ne = [(max_x,max_y,ne_z[0]),ne_z[1]] 

c = [[(mid_x,mid_y,mid_z[0]),mid_z[1]]] 

 

 

left = [sw,nw] 

left_c = c 

top = [nw,ne] 

top_c = c 

right = [se,ne] 

right_c = c 

bottom = [sw,se] 

bottom_c = c 

 

return PlotBlock(left, left_c, top, top_c, right, right_c, bottom, bottom_c) 

 

def interface(self, n, p, p_c, q, q_c): 

""" 

Takes a pair of lists of points, and compares the (n)th coordinate, and 

"zips" the lists together into one. The "centers", supplied in p_c and 

q_c are matched up such that the lists describe triangles whose sides 

are "perfectly" aligned. This algorithm assumes that p and q start and 

end at the same point, and are sorted smallest to largest. 

 

TESTS:: 

 

sage: from sage.plot.plot3d.tri_plot import TrianglePlot, TriangleFactory 

sage: tf = TriangleFactory() 

sage: t = TrianglePlot(tf, lambda x,y: x^2 - y*x, (0, -2), (0, 2), max_depth=3) 

sage: t.interface(1, [[(-1/4, 0, 1/16)], [(-1/4, 1/4, 1/8)]], [[(-1/8, 1/8, 1/32)]], [[(-1/4, 0, 1/16)], [(-1/4, 1/4, 1/8)]], [[(-3/8, 1/8, 3/16)]]) 

sage: t._objects[-1].get_vertices() 

((-1/4, 0, 1/16), (-1/4, 1/4, 1/8), (-3/8, 1/8, 3/16)) 

""" 

m = [p[0]] # a sorted union of p and q 

mpc = [p_c[0]] # centers from p_c corresponding to m 

mqc = [q_c[0]] # centers from q_c corresponding to m 

 

i = 1 

j = 1 

 

while i < len(p_c) or j < len(q_c): 

if p[i][0][n] == q[j][0][n]: 

m.append(p[i]) 

mpc.append(p_c[i]) 

mqc.append(q_c[j]) 

i += 1 

j += 1 

elif p[i][0][n] < q[j][0][n]: 

m.append(p[i]) 

mpc.append(p_c[i]) 

mqc.append(mqc[-1]) 

i += 1 

else: 

m.append(q[j]) 

mpc.append(mpc[-1]) 

mqc.append(q_c[j]) 

j += 1 

 

m.append(p[-1]) 

 

self.triangulate(m, mpc) 

self.triangulate(m, mqc) 

 

 

def triangulate(self, p, c): 

""" 

Pass in a list of edge points (p) and center points (c). 

Triangles will be rendered between consecutive edge points and the 

center point with the same index number as the earlier edge point. 

 

TESTS:: 

 

sage: from sage.plot.plot3d.tri_plot import TrianglePlot, TriangleFactory 

sage: tf = TriangleFactory() 

sage: t = TrianglePlot(tf, lambda x,y: x^2 - y*x, (0, -2), (0, 2)) 

sage: t.triangulate([[[1,0,0],[0,0,1]],[[0,1,1],[1,1,1]]],[[[0,3,1]]]) 

sage: t._objects[-1].get_vertices() 

([1, 0, 0], [0, 1, 1], [0, 3, 1]) 

""" 

 

if self._g is None: 

for i in range(0,len(p)-1): 

self._objects.append(self._triangle_factory.triangle(p[i][0], p[i+1][0], c[i][0])) 

else: 

for i in range(0,len(p)-1): 

self._objects.append(self._triangle_factory.smooth_triangle(p[i][0], p[i+1][0], c[i][0],p[i][1], p[i+1][1], c[i][1])) 

 

 

def extrema(self, list): 

""" 

If the num_colors option has been set, this expands the TrianglePlot's _min and _max 

attributes to include the minimum and maximum of the argument list. 

 

TESTS:: 

 

sage: from sage.plot.plot3d.tri_plot import TrianglePlot, TriangleFactory 

sage: tf = TriangleFactory() 

sage: t = TrianglePlot(tf, lambda x,y: x^2+y^2, (0, 1), (0, 1), num_colors = 3) 

sage: t._min, t._max 

(0, 2) 

sage: t.extrema([-1,2,3,4]) 

sage: t._min, t._max 

(-1, 4) 

""" 

if self._num_colors is not None: 

self._min = min(list+[self._min]) 

self._max = max(list+[self._max]) 

 

 

def crossunit(u,v): 

""" 

This function computes triangle normal unit vectors by taking the 

cross-products of the midpoint-to-corner vectors. It always goes 

around clockwise so we're guaranteed to have a positive value near 

1 when neighboring triangles are parallel. However -- crossunit 

doesn't really return a unit vector. It returns the length of the 

vector to avoid numerical instability when the length is nearly zero 

-- rather than divide by nearly zero, we multiply the other side of 

the inequality by nearly zero -- in general, this should work a bit 

better because of the density of floating-point numbers near zero. 

 

TESTS:: 

 

sage: from sage.plot.plot3d.tri_plot import crossunit 

sage: crossunit([0,-1,0],[0,0,1]) 

(-1, 0, 0, 1.0) 

""" 

p = (u[1]*v[2] - v[1]*u[2], u[0]*v[2] - v[0]*u[2], u[0]*v[1] - u[1]*v[0]) 

l = sqrt(p[0]**2 + p[1]**2 + p[2]**2) 

return (p[0], p[1], p[2], l) 

 

 

class PlotBlock: 

""" 

A container class to hold information about spatial blocks. 

""" 

def __init__(self, left, left_c, top, top_c, right, right_c, bottom, bottom_c): 

""" 

 

TESTS:: 

 

sage: from sage.plot.plot3d.tri_plot import PlotBlock 

sage: pb = PlotBlock([0,0,0],[0,1,0],[0,0,1],[0,0,.5],[1,0,0],[1,1,0],[-2,-2,0],[0,0,0]) 

sage: pb.left 

[0, 0, 0] 

""" 

self.left = left 

self.left_c = left_c 

self.top = top 

self.top_c = top_c 

self.right = right 

self.right_c = right_c 

self.bottom = bottom 

self.bottom_c = bottom_c